From 5d1d33390b6b5b012c112d0b04f3fc6e75bdabdc Mon Sep 17 00:00:00 2001 From: "John E. Malmberg" Date: Sat, 4 Apr 2026 14:27:04 -0500 Subject: [PATCH] Improve Serial port detection. Instead of trying to scan the serial ports with platform specific methods, use the python provided serial.tools.list_ports - changes/309.bugfix - changes/310.bugfix Tickets fixed by this PR. - d_rats/dplatform_generic.py Add platform independent list of serial ports - d_rats/dplatform_macos.py - d_rats/dplatform_unix.py - d_rats/dplatform_win32.py Remove no longer needed serial port listing method. Original Author: Github user Initram --- d_rats/dplatform_generic.py | 28 +++++++++++----------- d_rats/dplatform_macos.py | 15 +----------- d_rats/dplatform_unix.py | 12 +--------- d_rats/dplatform_win32.py | 48 +------------------------------------ 4 files changed, 17 insertions(+), 86 deletions(-) diff --git a/d_rats/dplatform_generic.py b/d_rats/dplatform_generic.py index 7deb384..4f05070 100755 --- a/d_rats/dplatform_generic.py +++ b/d_rats/dplatform_generic.py @@ -20,13 +20,15 @@ import logging # import mimetypes import os -import shutil +from shutil import which import subprocess import sys +from contextlib import contextmanager +from urllib.request import urlretrieve +from serial.tools.list_ports import comports # Need all of these packages for sound to work on a generic platform # The pydub package not currently available on msys2 mingw64. -from contextlib import contextmanager HAVE_AUDIO = False if not os.name == "nt": try: @@ -57,9 +59,6 @@ def play(sound): sound_type = type(sound) print(f'Unable to play sound {sound_type} is unsupported') - -import urllib.request - # This version of D-Rats requires GTK 3.0, but not for doing unit # tests. This allows the default unit tests to pass on a system with # out GTK+ installed. @@ -134,7 +133,6 @@ def __str__(self): return os.linesep.join(text) def set_config_dir(self, basepath): - ''' Set the configuration directory. @@ -200,14 +198,14 @@ def get_exe_path(name): ''' # Make trivial check from the normal paths # known false positive in pylance - exe_path = shutil.which(name) # type: ignore + exe_path = which(name) # type: ignore if exe_path: return exe_path programfiles = os.getenv("PROGRAMFILES") if programfiles: for program in [programfiles, programfiles + " (x86)"]: test_path=os.path.join(program, name) - exe_path = shutil.which(name, path=test_path) # type: ignore + exe_path = which(name, path=test_path) # type: ignore if exe_path: return exe_path return None @@ -285,15 +283,17 @@ def open_html_file(path): gio_path = Gio.File.parse_name(path) appinfo.launch([gio_path], None) - def list_serial_ports(self): + @staticmethod + def list_serial_ports(): ''' List Serial Ports. - :returns: empty list - :rtype: list + :returns: The serial ports + :rtype: list of str ''' - self.logger.info("list_serial_ports not available on this platform") - return [] + ports = [port.device for port in comports()] + return ports + @staticmethod def default_dir(): @@ -491,7 +491,7 @@ def retrieve_url(self, url): :returns: Data from URL ''' if self._connected: - return urllib.request.urlretrieve(url) + return urlretrieve(url) raise NotConnectedError("Not connected") diff --git a/d_rats/dplatform_macos.py b/d_rats/dplatform_macos.py index 8b11cbd..1253a81 100755 --- a/d_rats/dplatform_macos.py +++ b/d_rats/dplatform_macos.py @@ -2,7 +2,7 @@ # # Copyright 2009 Dan Smith # review 2015 Maurizio Andreotti -# Copyright 2021-2023 John. E. Malmberg - Python3 Conversion +# Copyright 2021-2023,2026 John. E. Malmberg - Python3 Conversion # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,7 +18,6 @@ # along with this program. If not, see . import logging -import glob import os import sys @@ -48,18 +47,6 @@ def __init__(self, basepath): UnixPlatform.__init__(self, basepath) - def list_serial_ports(self): - ''' - List Serial Ports. - - :returns: serial port names - :rtype: list[str] - ''' - keyspan = glob.glob("/dev/cu.KeySerial*") - prolific = glob.glob("/dev/tty.usbserial*") - - return sorted(keyspan + prolific) - def os_version_string(self): ''' OS Version String. diff --git a/d_rats/dplatform_unix.py b/d_rats/dplatform_unix.py index 61e84cf..467d4b4 100755 --- a/d_rats/dplatform_unix.py +++ b/d_rats/dplatform_unix.py @@ -2,7 +2,7 @@ # # Copyright 2009 Dan Smith # review 2015 Maurizio Andreotti -# Copyright 2021-2023 John. E. Malmberg - Python3 Conversion +# Copyright 2021-2023,2026 John. E. Malmberg - Python3 Conversion # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,7 +18,6 @@ # along with this program. If not, see . import logging -import glob import os import subprocess @@ -79,15 +78,6 @@ def filter_filename(filename): ''' return filename.replace("/", "") - def list_serial_ports(self): - ''' - List Serial Ports. - - :returns: The serial ports - :rtype: list of str - ''' - return sorted(glob.glob("/dev/ttyS*") + glob.glob("/dev/ttyUSB*")) - def os_version_string(self): ''' OS Version String. diff --git a/d_rats/dplatform_win32.py b/d_rats/dplatform_win32.py index 489179a..b944ade 100755 --- a/d_rats/dplatform_win32.py +++ b/d_rats/dplatform_win32.py @@ -2,7 +2,7 @@ # # Copyright 2009 Dan Smith # review 2015 Maurizio Andreotti -# Copyright 2021-2023 John. E. Malmberg - Python3 Conversion +# Copyright 2021-2023,2026 John. E. Malmberg - Python3 Conversion # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -38,14 +38,6 @@ from win32com.shell import shell # type: ignore except ImportError: pass -try: - import win32con # type: ignore -except ImportError: - pass -try: - import win32file # type: ignore -except ImportError: - pass try: import win32gui # type: ignore except ImportError: @@ -121,44 +113,6 @@ def filter_filename(filename): return filename - def list_serial_ports(self): - ''' - List Serial Ports. - - :returns: List of serial ports - :rtype: list[str] - ''' - ports = [] - try: - for i in range(1, 257): - try: - portname = "COM%i" % i - mode = win32con.GENERIC_READ # type: ignore - mode |= win32con.GENERIC_WRITE # type: ignore - port = win32file.CreateFile( # type: ignore - portname, - mode, - win32con.FILE_SHARE_READ, # type: ignore - None, - win32con.OPEN_EXISTING, # type: ignore - 0, - None) - ports.append(portname) - win32file.CloseHandle(port) # type: ignore - port = None - - # On cross platform IDEs pylint may false positive. - # pylint: disable=no-member - except pywintypes.error as err: # type: ignore - # Error code 5 Apparently if the serial port is in use. - # Error code 121 Apparently if timeout in operation. - if err.args[0] not in [2, 5, 121]: - self.logger.info("list_serial_ports", exc_info=True) - except NameError: - self.logger.info("Unable to look up serial ports, " - "win32con or other python package missing!") - return ports - @staticmethod def _mime_to_filter(mime_types): '''