使用Python列出可用的COM端口

125
我正在寻找一种简单的方法来列出PC上所有可用的串口。我已经找到了这个方法,但它只适用于Windows系统:Listing serial (COM) ports on Windows?
我正在使用Python 3和pySerial在Windows 7 PC上。在pySerial API (http://pyserial.sourceforge.net/pyserial_api.html)中,我发现有一个函数serial.tools.list_ports.comports()可以列出串口(正好是我想要的)。
import serial.tools.list_ports
print(list(serial.tools.list_ports.comports()))

但是看起来它不起作用。当我的USB转COM网关连接到PC时(我在设备管理器中看到COM5),这个COM端口没有被包括在list_ports.comports()返回的列表中。相反,我只得到了似乎连接到调制解调器上的COM4(在设备管理器的COM&LPT部分中看不到它)!

你知道为什么它不工作吗?你有没有另一种不特定于系统的解决方案?


56
注意:新读者需要知道,自从提出这个问题已经超过五年了,pySerial的comports()函数中描述的错误(没有精确的重现方法)可能已经被修复。首先尝试使用import serial.tools.list_ports; print([comport.device for comport in serial.tools.list_ports.comports()])。只有当这个不适用于你时,下面任何一个答案才会对你有用。 - Mark Amery
5
对于新读者:显然由于pySerial的更改,OP(和一些答案)所描述的代码不再生成完整或不完整的COM端口名称列表。相反,它生成了指向“ListPortInfo”对象的对象引用列表。在构建列表时,要获取名称或其他信息,必须使用这些对象的属性。请参阅:https://pythonhosted.org/pyserial/tools.html#serial.tools.list_ports.ListPortInfo - JDM
13个回答

192

这是我使用的代码。

在 Windows 8.1 x64、Windows 10 x64、Mac OS X 10.9.x / 10.10.x / 10.11.x 和 Ubuntu 14.04 / 14.10 / 15.04 / 15.10 上,已经成功测试过 Python 2 和 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())

1
我可能会添加一些系统版本检测,然后就可以结束了。我不知道“所有系统”是否都相同,因为根据您的操作系统,端口获取方式可能会有所不同。我只需检测操作系统版本,然后根据情况提供一个可用于各种情况的开关即可。 - jml
1
当我在OSX上测试时,我还需要添加except OSError:pass - nvahalik
8
如果一个端口已经打开,会发生什么?它会返回一个错误,对吗?但是这个端口仍然可用。我只是想知道如何在Windows上使用Python实现可行的COM端口断开连接。 - Manny42
@Manny42,我不明白你想要实现什么,但如果一个端口已经打开,它肯定不会对其他程序可用,并且不会被serial_ports()函数列出(也不会引发异常)。为什么不使用pyserial提供的close()函数来断开COM端口的连接呢? - tfeldmann
1
今天在我的 Raspberry Pi Zero W V1.1(Python 3)上毫无问题地运行,没有任何修改! - TTorai
显示剩余6条评论

63

基本上在pyserial文档中提到过这个 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))

结果:

COM1:通信端口(COM1)[ACPI\PNP0501\1]

COM7:联发科技 USB 端口(COM7)[USB VID:PID=0E8D:0003 SER=6 LOCATION=1-2.1]


30

这在OSX 10.11.5上使用python 2.7.11非常好用,似乎比Thomas的解决方案快得多。 在使用python 3.4.4的OSX 10.11.5上,它返回一个空数组,因此肯定缺少一些串口。 - doizuc
在Windows7和Python 2.7上运行良好。 非常有用,它返回一个类似于这样的元组: ('COM114','USB串行端口(COM114)','FTDIBUS \\ VID_0403 + PID_6001 + 7&2A8DEF85&0&2 \\ 0000') 这使您可以按供应商USB PID / VID进行过滤,这正是我想要的。 - Richard Aplin
刚在Linux板上(Debian,3.4内核)进行了测试,结果同样良好,这次包括USB设备序列号 ('/dev/ttyACM1', 'ttyACM1', 'USB VID:PID=0483:5752 SNR=8D7B179D5354') 非常好的解决方案。谢谢! - Richard Aplin
这里有一个非常相似的答案: https://dev59.com/pWAf5IYBdhLWcg3w2Voo - Gladclef
3
-1;OP已经提到了serial.tools.list_ports.comports()并解释说它在提问者的平台上无法正常工作。你没有添加任何问题中没有的信息。 - Mark Amery

21

使用 pySerial 包的一行解决方案。

python -m serial.tools.list_ports

1
就我而言,这是最简单的答案。 - Dan Hoover

12

对于Thomas出色回答的可能改善是让Linux和可能的OSX也尝试打开端口,并且只返回可打开的端口。这是因为Linux至少在/dev/中列出了一大堆未连接到任何东西的端口文件。如果您正在终端上运行,/dev/tty是您正在工作的终端,打开和关闭它可能会捣乱您的命令行,因此该通配符不会执行该操作。

    # ... 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

这个对Thomas的代码的修改仅在Ubuntu 14.04上进行了测试。


10

moylop260答案的进一步改进:

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))

此处列出了硬件中存在的端口,包括正在使用的端口。该列表中存在更多信息,请参考pyserial工具文档


4
至少有两个原因让它不可取:一是覆盖内置函数名字(如list)是不好的编码习惯,二是你的 connected 变量可以更简洁地写成这样:connected = [port.device for port in serial.tools.list_ports.comports()] - Mark Amery
9
对于那些已经在嵌入式C上花费了半个职业生涯的人来说,我不喜欢单行代码包含所有功能的风格。当然,你可以将所有东西都写在一行上。我已经更正了“list”的失误,不确定我怎么会错过这样一个显眼的错误。干杯! - grambo
6
你认为在问答网站上使用一句话的提问方式是一个好主意吗? - cstrutton
6
@Mark Avery. 一句话难以让初学者理解关键概念。例如,如果你使用列表推导式来演示一个简单的概念,新手可能会迷失在列表推导式中而错过基本概念。如果列表推导式是最好的方法,先展示长版本,然后再展示如何缩短。这样可以教授你的观点并同时加强列表推导式的使用方法。 - cstrutton
@cstrutton 嗯,依我看来,理解式是最易读的写法,但也许你说得对,Python新手可能更喜欢这种方式。我认为没有太多理由同时展示两种写法——教授理解式并不是回答的重点,目标应该是尽可能让读者理解正在发生的事情。 - Mark Amery
显示剩余3条评论

6

可能有些晚了,但希望能帮到需要的人。

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"))

4

请尝试这段代码:

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

首先,您需要导入串口通信的程序包,因此:
import serial

然后您需要创建当前所有可用串口的列表:
ports = serial.tools.list_ports.comports(include_links=False)

然后,沿着整个列表走,你可以打印端口名称:

for port in ports :
    print(port.device)

这只是一个获取端口列表并打印名称的示例,但您可以使用此数据进行其他操作。尝试在“

port.

”后打印不同的变量。

我觉得你需要执行"import serial.tools.list_ports",或者至少在Python 3.7中使用PySerial 3.4版本时,这是我所需做的。 - Tom Myddeltyn

4

这是一个简单的东西,但我经常使用它。

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'        

2

需要注意的是,像这样的代码:

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

返回以下内容:

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

如果您希望以顺序列出端口,并只列出对您可用的端口,请尝试以下方法:(感谢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

这将返回以下内容:

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

所以与第一个示例不同,结果为['COM7','COM1','COM8','COM4'],这次我按顺序获取所有串口,并仅获取可用的串口。如果您需要按顺序使用它们并测试它们是否可用,这非常方便。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接