diff options
author | Clyne Sullivan <clyne@clyne-lp.lan> | 2021-08-08 22:02:52 -0400 |
---|---|---|
committer | Clyne Sullivan <clyne@clyne-lp.lan> | 2021-08-08 22:02:52 -0400 |
commit | 707b24dd07236243269cf092728f85172e94e8a4 (patch) | |
tree | c136716a5fc9ed9cbf570e24f8f6ab715adc73a2 /source/serial/src/impl/list_ports |
initial commit
Diffstat (limited to 'source/serial/src/impl/list_ports')
-rw-r--r-- | source/serial/src/impl/list_ports/list_ports_linux.cc | 335 | ||||
-rw-r--r-- | source/serial/src/impl/list_ports/list_ports_osx.cc | 286 | ||||
-rw-r--r-- | source/serial/src/impl/list_ports/list_ports_win.cc | 152 |
3 files changed, 773 insertions, 0 deletions
diff --git a/source/serial/src/impl/list_ports/list_ports_linux.cc b/source/serial/src/impl/list_ports/list_ports_linux.cc new file mode 100644 index 0000000..9779d5e --- /dev/null +++ b/source/serial/src/impl/list_ports/list_ports_linux.cc @@ -0,0 +1,335 @@ +#if defined(__linux__) + +/* + * Copyright (c) 2014 Craig Lilley <cralilley@gmail.com> + * This software is made available under the terms of the MIT licence. + * A copy of the licence can be obtained from: + * http://opensource.org/licenses/MIT + */ + +#include <vector> +#include <string> +#include <sstream> +#include <stdexcept> +#include <iostream> +#include <fstream> +#include <cstdio> +#include <cstdarg> +#include <cstdlib> + +#include <glob.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "serial/serial.h" + +using serial::PortInfo; +using std::istringstream; +using std::ifstream; +using std::getline; +using std::vector; +using std::string; +using std::cout; +using std::endl; + +static vector<string> glob(const vector<string>& patterns); +static string basename(const string& path); +static string dirname(const string& path); +static bool path_exists(const string& path); +static string realpath(const string& path); +static string usb_sysfs_friendly_name(const string& sys_usb_path); +static vector<string> get_sysfs_info(const string& device_path); +static string read_line(const string& file); +static string usb_sysfs_hw_string(const string& sysfs_path); +static string format(const char* format, ...); + +vector<string> +glob(const vector<string>& patterns) +{ + vector<string> paths_found; + + if(patterns.size() == 0) + return paths_found; + + glob_t glob_results; + + int glob_retval = glob(patterns[0].c_str(), 0, NULL, &glob_results); + + vector<string>::const_iterator iter = patterns.begin(); + + while(++iter != patterns.end()) + { + glob_retval = glob(iter->c_str(), GLOB_APPEND, NULL, &glob_results); + } + + for(int path_index = 0; path_index < glob_results.gl_pathc; path_index++) + { + paths_found.push_back(glob_results.gl_pathv[path_index]); + } + + globfree(&glob_results); + + return paths_found; +} + +string +basename(const string& path) +{ + size_t pos = path.rfind("/"); + + if(pos == std::string::npos) + return path; + + return string(path, pos+1, string::npos); +} + +string +dirname(const string& path) +{ + size_t pos = path.rfind("/"); + + if(pos == std::string::npos) + return path; + else if(pos == 0) + return "/"; + + return string(path, 0, pos); +} + +bool +path_exists(const string& path) +{ + struct stat sb; + + if( stat(path.c_str(), &sb ) == 0 ) + return true; + + return false; +} + +string +realpath(const string& path) +{ + char* real_path = realpath(path.c_str(), NULL); + + string result; + + if(real_path != NULL) + { + result = real_path; + + free(real_path); + } + + return result; +} + +string +usb_sysfs_friendly_name(const string& sys_usb_path) +{ + unsigned int device_number = 0; + + istringstream( read_line(sys_usb_path + "/devnum") ) >> device_number; + + string manufacturer = read_line( sys_usb_path + "/manufacturer" ); + + string product = read_line( sys_usb_path + "/product" ); + + string serial = read_line( sys_usb_path + "/serial" ); + + if( manufacturer.empty() && product.empty() && serial.empty() ) + return ""; + + return format("%s %s %s", manufacturer.c_str(), product.c_str(), serial.c_str() ); +} + +vector<string> +get_sysfs_info(const string& device_path) +{ + string device_name = basename( device_path ); + + string friendly_name; + + string hardware_id; + + string sys_device_path = format( "/sys/class/tty/%s/device", device_name.c_str() ); + + if( device_name.compare(0,6,"ttyUSB") == 0 ) + { + sys_device_path = dirname( dirname( realpath( sys_device_path ) ) ); + + if( path_exists( sys_device_path ) ) + { + friendly_name = usb_sysfs_friendly_name( sys_device_path ); + + hardware_id = usb_sysfs_hw_string( sys_device_path ); + } + } + else if( device_name.compare(0,6,"ttyACM") == 0 ) + { + sys_device_path = dirname( realpath( sys_device_path ) ); + + if( path_exists( sys_device_path ) ) + { + friendly_name = usb_sysfs_friendly_name( sys_device_path ); + + hardware_id = usb_sysfs_hw_string( sys_device_path ); + } + } + else + { + // Try to read ID string of PCI device + + string sys_id_path = sys_device_path + "/id"; + + if( path_exists( sys_id_path ) ) + hardware_id = read_line( sys_id_path ); + } + + if( friendly_name.empty() ) + friendly_name = device_name; + + if( hardware_id.empty() ) + hardware_id = "n/a"; + + vector<string> result; + result.push_back(friendly_name); + result.push_back(hardware_id); + + return result; +} + +string +read_line(const string& file) +{ + ifstream ifs(file.c_str(), ifstream::in); + + string line; + + if(ifs) + { + getline(ifs, line); + } + + return line; +} + +string +format(const char* format, ...) +{ + va_list ap; + + size_t buffer_size_bytes = 256; + + string result; + + char* buffer = (char*)malloc(buffer_size_bytes); + + if( buffer == NULL ) + return result; + + bool done = false; + + unsigned int loop_count = 0; + + while(!done) + { + va_start(ap, format); + + int return_value = vsnprintf(buffer, buffer_size_bytes, format, ap); + + if( return_value < 0 ) + { + done = true; + } + else if( return_value >= buffer_size_bytes ) + { + // Realloc and try again. + + buffer_size_bytes = return_value + 1; + + char* new_buffer_ptr = (char*)realloc(buffer, buffer_size_bytes); + + if( new_buffer_ptr == NULL ) + { + done = true; + } + else + { + buffer = new_buffer_ptr; + } + } + else + { + result = buffer; + done = true; + } + + va_end(ap); + + if( ++loop_count > 5 ) + done = true; + } + + free(buffer); + + return result; +} + +string +usb_sysfs_hw_string(const string& sysfs_path) +{ + string serial_number = read_line( sysfs_path + "/serial" ); + + if( serial_number.length() > 0 ) + { + serial_number = format( "SNR=%s", serial_number.c_str() ); + } + + string vid = read_line( sysfs_path + "/idVendor" ); + + string pid = read_line( sysfs_path + "/idProduct" ); + + return format("USB VID:PID=%s:%s %s", vid.c_str(), pid.c_str(), serial_number.c_str() ); +} + +vector<PortInfo> +serial::list_ports() +{ + vector<PortInfo> results; + + vector<string> search_globs; + search_globs.push_back("/dev/ttyACM*"); + search_globs.push_back("/dev/ttyS*"); + search_globs.push_back("/dev/ttyUSB*"); + search_globs.push_back("/dev/tty.*"); + search_globs.push_back("/dev/cu.*"); + + vector<string> devices_found = glob( search_globs ); + + vector<string>::iterator iter = devices_found.begin(); + + while( iter != devices_found.end() ) + { + string device = *iter++; + + vector<string> sysfs_info = get_sysfs_info( device ); + + string friendly_name = sysfs_info[0]; + + string hardware_id = sysfs_info[1]; + + PortInfo device_entry; + device_entry.port = device; + device_entry.description = friendly_name; + device_entry.hardware_id = hardware_id; + + results.push_back( device_entry ); + + } + + return results; +} + +#endif // defined(__linux__) diff --git a/source/serial/src/impl/list_ports/list_ports_osx.cc b/source/serial/src/impl/list_ports/list_ports_osx.cc new file mode 100644 index 0000000..333c55c --- /dev/null +++ b/source/serial/src/impl/list_ports/list_ports_osx.cc @@ -0,0 +1,286 @@ +#if defined(__APPLE__) + +#include <sys/param.h> +#include <stdint.h> + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/serial/IOSerialKeys.h> +#include <IOKit/IOBSD.h> + +#include <iostream> +#include <string> +#include <vector> + +#include "serial/serial.h" + +using serial::PortInfo; +using std::string; +using std::vector; + +#define HARDWARE_ID_STRING_LENGTH 128 + +string cfstring_to_string( CFStringRef cfstring ); +string get_device_path( io_object_t& serial_port ); +string get_class_name( io_object_t& obj ); +io_registry_entry_t get_parent_iousb_device( io_object_t& serial_port ); +string get_string_property( io_object_t& device, const char* property ); +uint16_t get_int_property( io_object_t& device, const char* property ); +string rtrim(const string& str); + +string +cfstring_to_string( CFStringRef cfstring ) +{ + char cstring[MAXPATHLEN]; + string result; + + if( cfstring ) + { + Boolean success = CFStringGetCString( cfstring, + cstring, + sizeof(cstring), + kCFStringEncodingASCII ); + + if( success ) + result = cstring; + } + + return result; +} + +string +get_device_path( io_object_t& serial_port ) +{ + CFTypeRef callout_path; + string device_path; + + callout_path = IORegistryEntryCreateCFProperty( serial_port, + CFSTR(kIOCalloutDeviceKey), + kCFAllocatorDefault, + 0 ); + + if (callout_path) + { + if( CFGetTypeID(callout_path) == CFStringGetTypeID() ) + device_path = cfstring_to_string( static_cast<CFStringRef>(callout_path) ); + + CFRelease(callout_path); + } + + return device_path; +} + +string +get_class_name( io_object_t& obj ) +{ + string result; + io_name_t class_name; + kern_return_t kern_result; + + kern_result = IOObjectGetClass( obj, class_name ); + + if( kern_result == KERN_SUCCESS ) + result = class_name; + + return result; +} + +io_registry_entry_t +get_parent_iousb_device( io_object_t& serial_port ) +{ + io_object_t device = serial_port; + io_registry_entry_t parent = 0; + io_registry_entry_t result = 0; + kern_return_t kern_result = KERN_FAILURE; + string name = get_class_name(device); + + // Walk the IO Registry tree looking for this devices parent IOUSBDevice. + while( name != "IOUSBDevice" ) + { + kern_result = IORegistryEntryGetParentEntry( device, + kIOServicePlane, + &parent ); + + if(kern_result != KERN_SUCCESS) + { + result = 0; + break; + } + + device = parent; + + name = get_class_name(device); + } + + if(kern_result == KERN_SUCCESS) + result = device; + + return result; +} + +string +get_string_property( io_object_t& device, const char* property ) +{ + string property_name; + + if( device ) + { + CFStringRef property_as_cfstring = CFStringCreateWithCString ( + kCFAllocatorDefault, + property, + kCFStringEncodingASCII ); + + CFTypeRef name_as_cfstring = IORegistryEntryCreateCFProperty( + device, + property_as_cfstring, + kCFAllocatorDefault, + 0 ); + + if( name_as_cfstring ) + { + if( CFGetTypeID(name_as_cfstring) == CFStringGetTypeID() ) + property_name = cfstring_to_string( static_cast<CFStringRef>(name_as_cfstring) ); + + CFRelease(name_as_cfstring); + } + + if(property_as_cfstring) + CFRelease(property_as_cfstring); + } + + return property_name; +} + +uint16_t +get_int_property( io_object_t& device, const char* property ) +{ + uint16_t result = 0; + + if( device ) + { + CFStringRef property_as_cfstring = CFStringCreateWithCString ( + kCFAllocatorDefault, + property, + kCFStringEncodingASCII ); + + CFTypeRef number = IORegistryEntryCreateCFProperty( device, + property_as_cfstring, + kCFAllocatorDefault, + 0 ); + + if(property_as_cfstring) + CFRelease(property_as_cfstring); + + if( number ) + { + if( CFGetTypeID(number) == CFNumberGetTypeID() ) + { + bool success = CFNumberGetValue( static_cast<CFNumberRef>(number), + kCFNumberSInt16Type, + &result ); + + if( !success ) + result = 0; + } + + CFRelease(number); + } + + } + + return result; +} + +string rtrim(const string& str) +{ + string result = str; + + string whitespace = " \t\f\v\n\r"; + + std::size_t found = result.find_last_not_of(whitespace); + + if (found != std::string::npos) + result.erase(found+1); + else + result.clear(); + + return result; +} + +vector<PortInfo> +serial::list_ports(void) +{ + vector<PortInfo> devices_found; + CFMutableDictionaryRef classes_to_match; + io_iterator_t serial_port_iterator; + io_object_t serial_port; + mach_port_t master_port; + kern_return_t kern_result; + + kern_result = IOMasterPort(MACH_PORT_NULL, &master_port); + + if(kern_result != KERN_SUCCESS) + return devices_found; + + classes_to_match = IOServiceMatching(kIOSerialBSDServiceValue); + + if (classes_to_match == NULL) + return devices_found; + + CFDictionarySetValue( classes_to_match, + CFSTR(kIOSerialBSDTypeKey), + CFSTR(kIOSerialBSDAllTypes) ); + + kern_result = IOServiceGetMatchingServices(master_port, classes_to_match, &serial_port_iterator); + + if (KERN_SUCCESS != kern_result) + return devices_found; + + while ( (serial_port = IOIteratorNext(serial_port_iterator)) ) + { + string device_path = get_device_path( serial_port ); + io_registry_entry_t parent = get_parent_iousb_device( serial_port ); + IOObjectRelease(serial_port); + + if( device_path.empty() ) + continue; + + PortInfo port_info; + port_info.port = device_path; + port_info.description = "n/a"; + port_info.hardware_id = "n/a"; + + string device_name = rtrim( get_string_property( parent, "USB Product Name" ) ); + string vendor_name = rtrim( get_string_property( parent, "USB Vendor Name") ); + string description = rtrim( vendor_name + " " + device_name ); + if( !description.empty() ) + port_info.description = description; + + string serial_number = rtrim(get_string_property( parent, "USB Serial Number" ) ); + uint16_t vendor_id = get_int_property( parent, "idVendor" ); + uint16_t product_id = get_int_property( parent, "idProduct" ); + + if( vendor_id && product_id ) + { + char cstring[HARDWARE_ID_STRING_LENGTH]; + + if(serial_number.empty()) + serial_number = "None"; + + int ret = snprintf( cstring, HARDWARE_ID_STRING_LENGTH, "USB VID:PID=%04x:%04x SNR=%s", + vendor_id, + product_id, + serial_number.c_str() ); + + if( (ret >= 0) && (ret < HARDWARE_ID_STRING_LENGTH) ) + port_info.hardware_id = cstring; + } + + devices_found.push_back(port_info); + } + + IOObjectRelease(serial_port_iterator); + return devices_found; +} + +#endif // defined(__APPLE__) diff --git a/source/serial/src/impl/list_ports/list_ports_win.cc b/source/serial/src/impl/list_ports/list_ports_win.cc new file mode 100644 index 0000000..7da40c4 --- /dev/null +++ b/source/serial/src/impl/list_ports/list_ports_win.cc @@ -0,0 +1,152 @@ +#if defined(_WIN32) + +/* + * Copyright (c) 2014 Craig Lilley <cralilley@gmail.com> + * This software is made available under the terms of the MIT licence. + * A copy of the licence can be obtained from: + * http://opensource.org/licenses/MIT + */ + +#include "serial/serial.h" +#include <tchar.h> +#include <windows.h> +#include <setupapi.h> +#include <initguid.h> +#include <devguid.h> +#include <cstring> + +using serial::PortInfo; +using std::vector; +using std::string; + +static const DWORD port_name_max_length = 256; +static const DWORD friendly_name_max_length = 256; +static const DWORD hardware_id_max_length = 256; + +// Convert a wide Unicode string to an UTF8 string +std::string utf8_encode(const std::wstring &wstr) +{ + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo( size_needed, 0 ); + WideCharToMultiByte (CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} + +vector<PortInfo> +serial::list_ports() +{ + vector<PortInfo> devices_found; + + HDEVINFO device_info_set = SetupDiGetClassDevs( + (const GUID *) &GUID_DEVCLASS_PORTS, + NULL, + NULL, + DIGCF_PRESENT); + + unsigned int device_info_set_index = 0; + SP_DEVINFO_DATA device_info_data; + + device_info_data.cbSize = sizeof(SP_DEVINFO_DATA); + + while(SetupDiEnumDeviceInfo(device_info_set, device_info_set_index, &device_info_data)) + { + device_info_set_index++; + + // Get port name + + HKEY hkey = SetupDiOpenDevRegKey( + device_info_set, + &device_info_data, + DICS_FLAG_GLOBAL, + 0, + DIREG_DEV, + KEY_READ); + + TCHAR port_name[port_name_max_length]; + DWORD port_name_length = port_name_max_length; + + LONG return_code = RegQueryValueEx( + hkey, + _T("PortName"), + NULL, + NULL, + (LPBYTE)port_name, + &port_name_length); + + RegCloseKey(hkey); + + if(return_code != EXIT_SUCCESS) + continue; + + if(port_name_length > 0 && port_name_length <= port_name_max_length) + port_name[port_name_length-1] = '\0'; + else + port_name[0] = '\0'; + + // Ignore parallel ports + + if(_tcsstr(port_name, _T("LPT")) != NULL) + continue; + + // Get port friendly name + + TCHAR friendly_name[friendly_name_max_length]; + DWORD friendly_name_actual_length = 0; + + BOOL got_friendly_name = SetupDiGetDeviceRegistryProperty( + device_info_set, + &device_info_data, + SPDRP_FRIENDLYNAME, + NULL, + (PBYTE)friendly_name, + friendly_name_max_length, + &friendly_name_actual_length); + + if(got_friendly_name == TRUE && friendly_name_actual_length > 0) + friendly_name[friendly_name_actual_length-1] = '\0'; + else + friendly_name[0] = '\0'; + + // Get hardware ID + + TCHAR hardware_id[hardware_id_max_length]; + DWORD hardware_id_actual_length = 0; + + BOOL got_hardware_id = SetupDiGetDeviceRegistryProperty( + device_info_set, + &device_info_data, + SPDRP_HARDWAREID, + NULL, + (PBYTE)hardware_id, + hardware_id_max_length, + &hardware_id_actual_length); + + if(got_hardware_id == TRUE && hardware_id_actual_length > 0) + hardware_id[hardware_id_actual_length-1] = '\0'; + else + hardware_id[0] = '\0'; + + #ifdef UNICODE + std::string portName = utf8_encode(port_name); + std::string friendlyName = utf8_encode(friendly_name); + std::string hardwareId = utf8_encode(hardware_id); + #else + std::string portName = port_name; + std::string friendlyName = friendly_name; + std::string hardwareId = hardware_id; + #endif + + PortInfo port_entry; + port_entry.port = portName; + port_entry.description = friendlyName; + port_entry.hardware_id = hardwareId; + + devices_found.push_back(port_entry); + } + + SetupDiDestroyDeviceInfoList(device_info_set); + + return devices_found; +} + +#endif // #if defined(_WIN32) |