diff --git a/CMakeLists.txt b/CMakeLists.txt index f7f6a7c..df06906 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,11 @@ project(serial) # Find catkin find_package(catkin REQUIRED) +if(APPLE) + find_library(IOKIT_LIBRARY IOKit) + find_library(FOUNDATION_LIBRARY Foundation) +endif() + if(UNIX AND NOT APPLE) # If Linux, add rt and pthread catkin_package( @@ -25,18 +30,25 @@ set(serial_SRCS include/serial/serial.h include/serial/v8stdint.h ) -if(UNIX) +if(APPLE) + # If OSX + list(APPEND serial_SRCS src/impl/unix.cc) + list(APPEND serial_SRCS src/impl/list_ports/list_ports_osx.cc) +elseif(UNIX) # If unix list(APPEND serial_SRCS src/impl/unix.cc) -else() + list(APPEND serial_SRCS src/impl/list_ports/list_ports_linux.cc) +elseif() # If windows list(APPEND serial_SRCS src/impl/win.cc) endif() ## Add serial library add_library(${PROJECT_NAME} ${serial_SRCS}) -if(UNIX AND NOT APPLE) - target_link_libraries(${PROJECT_NAME} rt) +if(APPLE) + target_link_libraries(${PROJECT_NAME} ${FOUNDATION_LIBRARY} ${IOKIT_LIBRARY}) +elseif(UNIX) + target_link_libraries(${PROJECT_NAME} rt pthread) endif() ## Uncomment for example diff --git a/examples/serial_example.cc b/examples/serial_example.cc index d2b922c..a829635 100644 --- a/examples/serial_example.cc +++ b/examples/serial_example.cc @@ -2,12 +2,12 @@ * This example expects the serial port has a loopback on it. * * Alternatively, you could use an Arduino: - * + * *
  *  void setup() {
  *    Serial.begin();
  *  }
- * 
+ *
  *  void loop() {
  *    if (Serial.available()) {
  *      Serial.write(Serial.read());
@@ -34,6 +34,7 @@ using std::exception;
 using std::cout;
 using std::cerr;
 using std::endl;
+using std::vector;
 
 void my_sleep(unsigned long milliseconds) {
 #ifdef _WIN32
@@ -43,16 +44,46 @@ void my_sleep(unsigned long milliseconds) {
 #endif
 }
 
+void enumerate_ports()
+{
+	vector devices_found = serial::list_ports();
+
+	vector::iterator iter = devices_found.begin();
+
+	while( iter != devices_found.end() )
+	{
+		serial::PortInfo device = *iter++;
+
+		printf( "(%s, %s, %s)\n", device.port.c_str(), device.description.c_str(),
+     device.hardware_id.c_str() );
+	}
+}
+
+void print_usage()
+{
+	cerr << "Usage: test_serial {-e|} ";
+    cerr << " [test string]" << endl;
+}
+
 int run(int argc, char **argv)
 {
-  if(argc < 3) {
-    cerr << "Usage: test_serial  ";
-    cerr << " [test string]" << endl;
+  if(argc < 2) {
+	  print_usage();
     return 0;
   }
-  // Argument 1 is the serial port
+
+  // Argument 1 is the serial port or enumerate flag
   string port(argv[1]);
 
+  if( port == "-e" ) {
+	  enumerate_ports();
+	  return 0;
+  }
+  else if( argc < 3 ) {
+	  print_usage();
+	  return 1;
+  }
+
   // Argument 2 is the baudrate
   unsigned long baud = 0;
 #if defined(WIN32) && !defined(__MINGW32__)
diff --git a/include/serial/serial.h b/include/serial/serial.h
index 25a9eb8..71e7c37 100644
--- a/include/serial/serial.h
+++ b/include/serial/serial.h
@@ -695,6 +695,32 @@ public:
   }
 };
 
+/*!
+ * Structure that describes a serial device.
+ */
+struct PortInfo {
+
+  /*! Address of the serial port (this can be passed to the constructor of Serial). */
+  std::string port;
+
+  /*! Human readable description of serial device if available. */
+  std::string description;
+
+  /*! Hardware ID (e.g. VID:PID of USB serial devices) or "n/a" if not available. */
+  std::string hardware_id;
+
+};
+
+/* Lists the serial ports available on the system
+ *
+ * Returns a vector of available serial ports, each represented
+ * by a serial::PortInfo data structure:
+ *
+ * \return vector of serial::PortInfo.
+ */
+std::vector
+list_ports();
+
 } // namespace serial
 
 #endif
diff --git a/src/impl/list_ports/list_ports_linux.cc b/src/impl/list_ports/list_ports_linux.cc
new file mode 100644
index 0000000..14c154b
--- /dev/null
+++ b/src/impl/list_ports/list_ports_linux.cc
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2014 Craig Lilley 
+ * 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 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#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 glob(const vector& 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 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
+glob(const vector& patterns)
+{
+    vector 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::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
+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 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
+serial::list_ports()
+{
+    vector results;
+
+    vector 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 devices_found = glob( search_globs );
+
+    vector::iterator iter = devices_found.begin();
+
+    while( iter != devices_found.end() )
+    {
+        string device = *iter++;
+
+        vector 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;
+}
diff --git a/src/impl/list_ports/list_ports_osx.cc b/src/impl/list_ports/list_ports_osx.cc
new file mode 100644
index 0000000..bf819d4
--- /dev/null
+++ b/src/impl/list_ports/list_ports_osx.cc
@@ -0,0 +1,281 @@
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#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(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(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(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
+serial::list_ports(void)
+{
+    vector 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);
+    }
+
+    return devices_found;
+}
\ No newline at end of file
diff --git a/src/impl/list_ports/list_ports_win.cc b/src/impl/list_ports/list_ports_win.cc
new file mode 100644
index 0000000..e792f93
--- /dev/null
+++ b/src/impl/list_ports/list_ports_win.cc
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2014 Craig Lilley 
+ * 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 
+#include 
+#include 
+#include 
+
+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;
+
+vector
+serial::list_ports()
+{
+	decltype( serial::list_ports() ) 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);
+
+		char port_name[port_name_max_length];
+		DWORD port_name_length = port_name_max_length;
+
+		LONG return_code = RegQueryValueEx(
+                    hkey,
+                    "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(strstr(port_name, "LPT") != NULL)
+			continue;
+
+		// Get port friendly name
+
+		char 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
+
+		char 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';
+
+		PortInfo port_entry;
+		port_entry.port = port_name;
+		port_entry.description = friendly_name;
+		port_entry.hardware_id = hardware_id;
+
+		devices_found.push_back(port_entry);
+	}
+
+	SetupDiDestroyDeviceInfoList(device_info_set);
+
+	return devices_found;
+}
diff --git a/visual_studio/serial/serial.vcxproj b/visual_studio/serial/serial.vcxproj
index b689734..0465ba7 100644
--- a/visual_studio/serial/serial.vcxproj
+++ b/visual_studio/serial/serial.vcxproj
@@ -46,6 +46,9 @@
     
       true
     
+    
+      setupapi.lib;%(AdditionalDependencies)
+    
   
   
     
@@ -61,6 +64,7 @@
     
   
   
+    
     
     
   
diff --git a/visual_studio/serial/serial.vcxproj.filters b/visual_studio/serial/serial.vcxproj.filters
index 40d4626..5034108 100644
--- a/visual_studio/serial/serial.vcxproj.filters
+++ b/visual_studio/serial/serial.vcxproj.filters
@@ -21,6 +21,9 @@
     
       Source Files
     
+    
+      Source Files
+    
   
   
     
diff --git a/visual_studio/serial/serial.vcxproj.user b/visual_studio/serial/serial.vcxproj.user
new file mode 100644
index 0000000..ace9a86
--- /dev/null
+++ b/visual_studio/serial/serial.vcxproj.user
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file