Listing available com ports with Python - python

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?

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

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]

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

one line solution with pySerial package.
python -m serial.tools.list_ports

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.

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

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

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.

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'

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

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

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.

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.

Related

Get RSSI of non connected bluetooth device

I am currently using raspberry pi and want to get RSSI of a non-connected Bluetooth address.
I am using
import bluetooth
result=bluetooth.lookup_name('XX:XX:XX:XX:XX:XX',timeout=5)
if(result !=None):
print("user near")
else:
print("user far")
but I want to be a little more precise and go to the else block in a closer distance and hence I need an RSSI value. Please help. I am new with raspberry and Python.
(I am working in python3)
Getting the RSSI value on a Raspberry Pi is supported by the BlueZ device API.
In the example below I have used pydbus as the library to access BlueZ's D-Bus API. This example scans for 60 seconds and writes the device address and RSSI value to a file. You could modify the code to take an action when a particular address and RSSI value is found.
from datetime import datetime
from pathlib import Path
import pydbus
from gi.repository import GLib
discovery_time = 60
log_file = Path('/home/pi/device.log')
def write_to_log(address, rssi):
"""Write device and rssi values to a log file"""
now = datetime.now()
current_time = now.strftime('%H:%M:%S')
with log_file.open('a') as dev_log:
dev_log.write(f'Device seen[{current_time}]: {address} # {rssi} dBm\n')
bus = pydbus.SystemBus()
mainloop = GLib.MainLoop()
class DeviceMonitor:
"""Class to represent remote bluetooth devices discovered"""
def __init__(self, path_obj):
self.device = bus.get('org.bluez', path_obj)
self.device.onPropertiesChanged = self.prop_changed
rssi = self.device.GetAll('org.bluez.Device1').get('RSSI')
if rssi:
print(f'Device added to monitor {self.device.Address} # {rssi} dBm')
else:
print(f'Device added to monitor {self.device.Address}')
def prop_changed(self, iface, props_changed, props_removed):
"""method to be called when a property value on a device changes"""
rssi = props_changed.get('RSSI', None)
if rssi is not None:
print(f'\tDevice Seen: {self.device.Address} # {rssi} dBm')
write_to_log(self.device.Address, rssi)
def end_discovery():
"""method called at the end of discovery scan"""
mainloop.quit()
adapter.StopDiscovery()
def new_iface(path, iface_props):
"""If a new dbus interfaces is a device, add it to be monitored"""
device_addr = iface_props.get('org.bluez.Device1', {}).get('Address')
if device_addr:
DeviceMonitor(path)
# BlueZ object manager
mngr = bus.get('org.bluez', '/')
mngr.onInterfacesAdded = new_iface
# Connect to the DBus api for the Bluetooth adapter
adapter = bus.get('org.bluez', '/org/bluez/hci0')
adapter.DuplicateData = False
# Iterate around already known devices and add to monitor
print('Adding already known device to monitor...')
mng_objs = mngr.GetManagedObjects()
for path in mng_objs:
device = mng_objs[path].get('org.bluez.Device1', {}).get('Address', [])
if device:
DeviceMonitor(path)
# Run discovery for discovery_time
adapter.StartDiscovery()
GLib.timeout_add_seconds(discovery_time, end_discovery)
print('Finding nearby devices...')
try:
mainloop.run()
except KeyboardInterrupt:
end_discovery()
If you need to install the gi.repository library then follow the "Installing the system provided PyGObject" for Debian instructions at: https://pygobject.readthedocs.io/en/latest/getting_started.html#ubuntu-getting-started
Bluepy library looks beneficial for RaspberryPI. Dont forget you should run like
"sudo python3 name.py" from terminal.
For more info: https://github.com/IanHarvey/bluepy/tree/master/docs
from bluepy.btle import Scanner
while True:
try:
#10.0 sec scanning
ble_list = Scanner().scan(10.0)
for dev in ble_list:
print("rssi: {} ; mac: {}".format(dev.rssi,dev.addr))
except:
raise Exception("Error occured")

GUID number of windows interface giving error: ValueError: Unknown network interface '{1619EEF1-4D71-4831-87AC-8E5DC3AA516A}'

Import scapy version 2.4.0. I am only using version 2.4.0 for my project
import scapy.all as scapy
import sys
by using IP address this function return related MAC address of the target
def get_mac(ip):
arp_request = scapy.ARP(pdst=ip)
broadcast = scapy.Ether(dst="ff:ff:ff:ff:ff:ff")
arp_request_broadcast = broadcast/arp_request
answered_list = scapy.srp(arp_request_broadcast, timeout=1, verbose=False)[0]
return answered_list[0][1].hwsrc
def sniff(interface):
scapy.sniff(iface=interface, store=False, prn=process_sniffed_packet)
This function checks whether default gateway MAC address is equal to my PC's MAC address table. if not it says "[+] You are under attack!!
def process_sniffed_packet(packet):
if packet.haslayer(scapy.ARP) and packet[scapy.ARP].op == 2:
count = 1
try:
real_mac = get_mac(packet[scapy.ARP].psrc)
response_mac = packet[scapy.ARP].hwsrc
if real_mac != response_mac:
count = count+1
print(str(count) + "[+] You are under attack!!")
sys.stdout.flush()
except IndexError:
pass
in Linux, we can use a value like 'etho' but In windows, I have to use GUID value to get the result. I am running this code in Windows Machine.
sniff('{1619EEF1-4D71-4831-87AC-8E5DC3AA516A}')
But this code return error
This is the Error that got raised
raise ValueError("Unknown network interface %r" % name)
ValueError: Unknown network interface '{1619EEF1-4D71-4831-87AC-
8E5DC3AA516A}'
On Windows, you need to provide a complete interface name / object, to be able to sniff on it.
First, have a look at what is available using IFACES.show() in a Scapy shell.
Then to get the interface, you can either use:
iface = IFACES.dev_from_name("...") (or dev_from_pcapname, dev_from_id... have a look at help(IFACES) to see what’s available)
iface = "the full name as printed above"
Then use it via sniff(iface=iface).
You could provide the pcap_name, but not the GUID: for instance, it would be something like \\Device\\NPF_{...} rather than just {...}.
Also, please use scapy 2.4.3rc1 (or at least 2.4.2) to be sure you’re up-to-date
I solved the scapy error ValueError: Unknown network interface on windows by installing npcap

Finding a specific serial COM port in pySerial (Windows)

I have a script built (Windows 7, Python 2.7) to list the serial ports but I'm looking for a device with a specific name.
My script:
import serial.tools.list_ports
ports = list(serial.tools.list_ports.comports())
for p in ports:
print(p)
This returns:
COM3 - Intel(R) Active Management Technology - SOL (COM3)
COM6 - MyCDCDevice (COM6)
COM1 - Communications Port (COM1)
>>>
Great! However, I want this script to automatically pick out MyCDCDevice from the bunch and connect to it.
I tried:
import serial.tools.list_ports
ports = list(serial.tools.list_ports.comports())
for p in ports:
if 'MyCDCDevice' in p:
print(p)
// do connection stuff to COM6
But that doesn't work. I suspect because p isn't exactly a string, but an object of some sort?
Anyways, what's the correct way to go about this?
Thanks!!
I know this post is very old, but I thought I would post my findings since there was no 'accepted' answer (better late than never).
This documentation helped with determining members of the object, and I eventually came to this solution.
import serial.tools.list_ports
ports = list(serial.tools.list_ports.comports())
for p in ports:
if 'MyCDCDevice' in p.description:
print(p)
# Connection to port
s = serial.Serial(p.device)
To further extend on this, I've found it safer to make use of the PID and VID of the device in question.
import serial.tools.list_ports
# FTDI FT232 device (http://www.linux-usb.org/usb.ids)
pid="0403"
hid="6001"
my_comm_port = None
ports = list(serial.tools.list_ports.comports())
for p in ports:
if pid and hid in p.hwid:
my_comm_port = p.device
Better still, you can use the serial number of the device for the lookup, just in case you have 2 of the same device plugged in.
(Source)
You can use serial.tools.list_ports.grep, which searches all of the description fields for you. For example:
from serial.tools import list_ports
try:
cdc = next(list_ports.grep("MyCDCDevice"))
# Do connection stuff on cdc
except StopIteration:
print "No device found"
If that doesn't work, you may try adding a * to the end of the string you pass to grep in case there are extra characters in the descriptor.

Finding Bluetooth low energy with python

Is it possible for this code to be modified to include Bluetooth Low Energy devices as well? https://code.google.com/p/pybluez/source/browse/trunk/examples/advanced/inquiry-with-rssi.py?r=1
I can find devices like my phone and other bluetooth 4.0 devices, but not any BLE. If this cannot be modified, is it possible to run the hcitool lescan and pull the data from hci dump within python? I can use the tools to see the devices I am looking for and it gives an RSSI in hcidump, which is what my end goal is. To get a MAC address and RSSI from the BLE device.
Thanks!
As I said in the comment, that library won't work with BLE.
Here's some example code to do a simple BLE scan:
import sys
import os
import struct
from ctypes import (CDLL, get_errno)
from ctypes.util import find_library
from socket import (
socket,
AF_BLUETOOTH,
SOCK_RAW,
BTPROTO_HCI,
SOL_HCI,
HCI_FILTER,
)
if not os.geteuid() == 0:
sys.exit("script only works as root")
btlib = find_library("bluetooth")
if not btlib:
raise Exception(
"Can't find required bluetooth libraries"
" (need to install bluez)"
)
bluez = CDLL(btlib, use_errno=True)
dev_id = bluez.hci_get_route(None)
sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)
sock.bind((dev_id,))
err = bluez.hci_le_set_scan_parameters(sock.fileno(), 0, 0x10, 0x10, 0, 0, 1000);
if err < 0:
raise Exception("Set scan parameters failed")
# occurs when scanning is still enabled from previous call
# allows LE advertising events
hci_filter = struct.pack(
"<IQH",
0x00000010,
0x4000000000000000,
0
)
sock.setsockopt(SOL_HCI, HCI_FILTER, hci_filter)
err = bluez.hci_le_set_scan_enable(
sock.fileno(),
1, # 1 - turn on; 0 - turn off
0, # 0-filtering disabled, 1-filter out duplicates
1000 # timeout
)
if err < 0:
errnum = get_errno()
raise Exception("{} {}".format(
errno.errorcode[errnum],
os.strerror(errnum)
))
while True:
data = sock.recv(1024)
# print bluetooth address from LE Advert. packet
print(':'.join("{0:02x}".format(x) for x in data[12:6:-1]))
I had to piece all of that together by looking at the hcitool and gatttool source code that comes with Bluez. The code is completely dependent on libbluetooth-dev so you'll have to make sure you have that installed first.
A better way would be to use dbus to make calls to bluetoothd, but I haven't had a chance to research that yet. Also, the dbus interface is limited in what you can do with a BLE connection after you make one.
EDIT:
Martin Tramšak pointed out that in Python 2 you need to change the last line to print(':'.join("{0:02x}".format(ord(x)) for x in data[12:6:-1]))
You could also try pygattlib. It can be used to discover devices, and (currently) there is a basic support for reading/writing characteristics. No RSSI for now.
You could discover using the following snippet:
from gattlib import DiscoveryService
service = DiscoveryService("hci0")
devices = service.discover(2)
DiscoveryService accepts the name of the device, and the method discover accepts a timeout (in seconds) for waiting responses. devices is a dictionary, with BL address as keys, and names as values.
pygattlib is packaged for Debian (or Ubuntu), and also available as a pip package.

how can the directory of a usb drive connected to a system be obtained?

I need to obtain the path to the directory created for a usb drive(I think it's something like /media/user/xxxxx) for a simple usb mass storage device browser that I am making. Can anyone suggest the best/simplest way to do this? I am using an Ubuntu 13.10 machine and will be using it on a linux device.
Need this in python.
This should get you started:
#!/usr/bin/env python
import os
from glob import glob
from subprocess import check_output, CalledProcessError
def get_usb_devices():
sdb_devices = map(os.path.realpath, glob('/sys/block/sd*'))
usb_devices = (dev for dev in sdb_devices
if 'usb' in dev.split('/')[5])
return dict((os.path.basename(dev), dev) for dev in usb_devices)
def get_mount_points(devices=None):
devices = devices or get_usb_devices() # if devices are None: get_usb_devices
output = check_output(['mount']).splitlines()
is_usb = lambda path: any(dev in path for dev in devices)
usb_info = (line for line in output if is_usb(line.split()[0]))
return [(info.split()[0], info.split()[2]) for info in usb_info]
if __name__ == '__main__':
print get_mount_points()
How does it work?
First, we parse /sys/block for sd* files (courtesy of https://stackoverflow.com/a/3881817/1388392) to filter out usb devices.
Later you call mount and parse output for lines only for those devices.
Of course they might be some edge cases, when this won't work, portability issues etc. Or better ways to do it. But for more information you should rather seek help on SuperUser or ServerFault, with more experienced linux hackers.
I had to modify #m.wasowski 's code to make it work on Python3.5.4 as follows.
def get_mount_points(devices=None):
devices = devices or get_usb_devices() # if devices are None: get_usb_devices
output = check_output(['mount']).splitlines()
output = [tmp.decode('UTF-8') for tmp in output]
def is_usb(path):
return any(dev in path for dev in devices)
usb_info = (line for line in output if is_usb(line.split()[0]))
return [(info.split()[0], info.split()[2]) for info in usb_info]
Using m.wasowski code, unexpected behavior can occur:
return [(info.split()[0], info.split()[2]) for info in usb_info]
This part of code can produce bug, if your USB device name has white space character in it. I got that behavior with device named "USB DEVICE".
info.split()[2]
Returned media/home/USB for me, when it is media/home/USB DEVICE.
I modified that part, so it was founding word "type", and replaced that line with this:
#return [(info.split()[0], info.split()[2]) for info in usb_info]
fullInfo = []
for info in usb_info:
print(info)
mountURI = info.split()[0]
usbURI = info.split()[2]
print(info.split().__sizeof__())
for x in range(3, info.split().__sizeof__()):
if info.split()[x].__eq__("type"):
for m in range(3, x):
usbURI += " "+info.split()[m]
break
fullInfo.append([mountURI, usbURI])
return fullInfo
I had to further modify #nick-sikrier and #m-wasowski response to handle LUKs encrypted devices.
def get_usb_devices():
sdb_devices = map(os.path.realpath, glob('/sys/block/sd*'))
usb_devices = (dev for dev in sdb_devices
if any(['usb' in dev.split('/')[5],
'usb' in dev.split('/')[6]]))
return dict((os.path.basename(dev), dev) for dev in usb_devices)
def get_mount_points(
devices = get_usb_devices()
fullInfo = []
for dev in devices:
output = subprocess.check_output(['lsblk', '-lnpo', 'NAME,MOUNTPOINT', '/dev/' + dev]).splitlines()
for mnt_point in output:
mnt_point_split = mnt_point.split(' ', 1)
if len(mnt_point_split) > 1 and mnt_point_split[1].strip():
fullInfo.append([mnt_point_split[0], mnt_point_split[1]])
return fullInfo
With a simple shell pipe executed in python:
import subprocess
driver_name = "my_usb_stick"
path = subprocess.check_output("cat /proc/mounts | grep '"+driver_name+"' | awk '{print $2}'", shell=True)
path = path.decode('utf-8') # convert bytes in string
>>> "/media/user/my_usb_stick"
Explanations
/proc/mounts/ : Is a file listing all mounted devices
The 1st column specifies the device that is mounted.
The 2nd column reveals the mount point.
The 3rd column tells the file-system type.
The 4th column tells you if it is mounted read-only (ro) or read-write (rw).
The 5th and 6th columns are dummy values designed to match the format used in /etc/mtab
More details see this answer : How to interpret /proc/mounts?
grep returns the line containing your driver's name
awk returns the 2nd columns, aka the mount point, aka your path.

Categories