在Ubuntu系统下使用C++获取USB设备序列号的方法
我需要读取USB设备的序列号,系统日志示例如下:
[89185.825492 < 68.912895>] usb 1-4: new full-speed USB device number 41 using xhci_hcd
[89185.975679 < 0.150187>] usb 1-4: New USB device found, idVendor=03eb, idProduct=2404, bcdDevice= 1.00
[89185.975685 < 0.000006>] usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[89185.975689 < 0.000004>] usb 1-4: Product: USB Roboclaw 2x30A
[89185.975692 < 0.000003>] usb 1-4: Manufacturer: Basicmicro Inc.
[89185.975695 < 0.000003>] usb 1-4: SerialNumber: 260
[89185.977910 < 0.002215>] cdc_acm 1-4:1.0: ttyACM0: USB ACM device
示例中目标序列号是260。我已经用以下代码实现了串口通信,但不清楚打开端口后如何获取这个序列号:
#include "TimeoutSerial.h" #include <string> #include <algorithm> #include <iostream> #include <boost/bind.hpp> using namespace std; using namespace boost; TimeoutSerial::TimeoutSerial(): io(), port(io), timer(io), timeout(boost::posix_time::seconds(0)) {} TimeoutSerial::TimeoutSerial(const std::string& devname, unsigned int baud_rate, asio::serial_port_base::parity opt_parity, asio::serial_port_base::character_size opt_csize, asio::serial_port_base::flow_control opt_flow, asio::serial_port_base::stop_bits opt_stop) : io(), port(io), timer(io), timeout(boost::posix_time::seconds(0)) { open(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop); } void TimeoutSerial::open(const std::string& devname, unsigned int baud_rate, asio::serial_port_base::parity opt_parity, asio::serial_port_base::character_size opt_csize, asio::serial_port_base::flow_control opt_flow, asio::serial_port_base::stop_bits opt_stop) { if(isOpen()) close(); port.open(devname); port.set_option(asio::serial_port_base::baud_rate(baud_rate)); port.set_option(opt_parity); port.set_option(opt_csize); port.set_option(opt_flow); port.set_option(opt_stop); } bool TimeoutSerial::isOpen() const { return port.is_open(); } void TimeoutSerial::close() { if(isOpen()==false) return; port.close(); } void TimeoutSerial::setTimeout(const boost::posix_time::time_duration& t) { timeout=t; } void TimeoutSerial::write(const char *data, size_t size) { asio::write(port,asio::buffer(data,size)); } void TimeoutSerial::write(const std::vector<char>& data) { asio::write(port,asio::buffer(&data[0],data.size())); } void TimeoutSerial::writeString(const std::string& s) { asio::write(port,asio::buffer(s.c_str(),s.size())); } void TimeoutSerial::read(char *data, size_t size) { if(readData.size()>0)//If there is some data from a previous read { istream is(&readData); size_t toRead=min(readData.size(),size);//How many bytes to read? is.read(data,toRead); data+=toRead; size-=toRead; if(size==0) return;//If read data was enough, just return } setupParameters=ReadSetupParameters(data,size); performReadSetup(setupParameters); //For this code to work, there should always be a timeout, so the //request for no timeout is translated into a very long timeout if(timeout!=boost::posix_time::seconds(0)) timer.expires_from_now(timeout); else timer.expires_from_now(boost::posix_time::hours(100000)); timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired,this, asio::placeholders::error)); result=resultInProgress; bytesTransferred=0; for(;;) { io.run_one(); switch(result) { case resultSuccess: timer.cancel(); return; case resultTimeoutExpired: port.cancel(); throw(timeout_exception("Timeout expired")); case resultError: timer.cancel(); port.cancel(); throw(boost::system::system_error(boost::system::error_code(), "Error while reading")); //if resultInProgress remain in the loop } } } std::vector<char> TimeoutSerial::read(size_t size) { vector<char> result(size,'\0');//Allocate a vector with the desired size read(&result[0],size);//Fill it with values return result; } std::string TimeoutSerial::readString(size_t size) { string result(size,'\0');//Allocate a string with the desired size read(&result[0],size);//Fill it with values return result; } std::string TimeoutSerial::readStringUntil(const std::string& delim) { // Note: if readData contains some previously read data, the call to // async_read_until (which is done in performReadSetup) correctly handles // it. If the data is enough it will also immediately call readCompleted() setupParameters=ReadSetupParameters(delim); performReadSetup(setupParameters); //For this code to work, there should always be a timeout, so the //request for no timeout is translated into a very long timeout if(timeout!=boost::posix_time::seconds(0)) timer.expires_from_now(timeout); else timer.expires_from_now(boost::posix_time::hours(100000)); timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired,this, asio::placeholders::error)); result=resultInProgress; bytesTransferred=0; for(;;) { io.run_one(); switch(result) { case resultSuccess: { timer.cancel(); bytesTransferred-=delim.size();//Don't count delim istream is(&readData); string result(bytesTransferred,'\0');//Alloc string is.read(&result[0],bytesTransferred);//Fill values is.ignore(delim.size());//Remove delimiter from stream return result; } case resultTimeoutExpired: port.cancel(); throw(timeout_exception("Timeout expired")); case resultError: timer.cancel(); port.cancel(); throw(boost::system::system_error(boost::system::error_code(), "Error while reading")); //if resultInProgress remain in the loop } } } TimeoutSerial::~TimeoutSerial() {} void TimeoutSerial::performReadSetup(const ReadSetupParameters& param) { if(param.fixedSize) { asio::async_read(port,asio::buffer(param.data,param.size),boost::bind( &TimeoutSerial::readCompleted,this,asio::placeholders::error, asio::placeholders::bytes_transferred)); } else { asio::async_read_until(port,readData,param.delim,boost::bind( &TimeoutSerial::readCompleted,this,asio::placeholders::error, asio::placeholders::bytes_transferred)); } } void TimeoutSerial::timeoutExpired(const boost::system::error_code& error) { if(!error && result==resultInProgress) result=resultTimeoutExpired; } void TimeoutSerial::readCompleted(const boost::system::error_code& error, const size_t bytesTransferred) { if(!error) { result=resultSuccess; this->bytesTransferred=bytesTransferred; return; } //In case a asynchronous operation is cancelled due to a timeout, //each OS seems to have its way to react. #ifdef _WIN32 if(error.value()==995) return; //Windows spits out error 995 #elif defined(__APPLE__) if(error.value()==45) { //Bug on OS X, it might be necessary to repeat the setup performReadSetup(setupParameters); return; } #else //Linux if(error.value()==125) return; //Linux outputs error 125 #endif result=resultError; }
请问是否有办法获取该序列号的值?
有两种可靠的方式获取这个USB设备的序列号,不需要依赖串口通信本身的交互:
1. 从系统sysfs文件读取(Linux环境)
你的设备是CDC-ACM类型串口(对应/dev/ttyACM0),Linux会在sysfs文件系统中维护所有USB设备的元数据,直接读取即可:
#include <fstream> #include <string> #include <filesystem> std::string get_usb_serial(const std::string& tty_device) { // 构建sysfs路径:从tty设备回溯到USB根设备 std::string sys_path = "/sys/class/tty/" + tty_device + "/device/../"; std::ifstream serial_file(sys_path + "serial"); std::string serial; if (serial_file.is_open()) { std::getline(serial_file, serial); serial_file.close(); } return serial; } // 使用示例:获取ttyACM0的序列号 std::string serial_num = get_usb_serial("ttyACM0");
这种方法无需打开串口,直接读取系统维护的设备信息,稳定且高效。
2. 通过设备协议指令读取
如果你的Roboclaw控制器支持串口指令查询序列号,可以查阅官方手册,发送对应指令后读取返回结果。比如Roboclaw通常有专用的参数查询指令,结合你现有的TimeoutSerial类实现:
// 示例:假设查询序列号的指令为0x82(需以官方手册为准) TimeoutSerial serial("/dev/ttyACM0", 115200); serial.setTimeout(boost::posix_time::seconds(1)); // 发送查询指令 uint8_t cmd = 0x82; serial.write(&cmd, 1); // 读取返回的序列号(假设返回3字节ASCII格式) std::string serial_num = serial.readString(3);
注意:具体指令格式、波特率、返回数据长度需要严格遵循Roboclaw 2x30A的官方文档。
3. 整合到现有TimeoutSerial类
如果需要把序列号读取功能整合到你的串口类中,可以添加成员函数:
// 在TimeoutSerial类中添加成员函数 std::string TimeoutSerial::getSerialNumber() const { if (!isOpen()) return ""; // 获取当前端口的设备名(如ttyACM0) std::string dev_path = port.native_handle(); size_t pos = dev_path.find_last_of('/'); std::string tty_name = dev_path.substr(pos + 1); // 调用第一种方法的逻辑读取序列号 std::string sys_path = "/sys/class/tty/" + tty_name + "/device/../"; std::ifstream serial_file(sys_path + "serial"); std::string serial; if (serial_file.is_open()) { std::getline(serial_file, serial); serial_file.close(); } return serial; }
内容的提问来源于stack exchange,提问作者Marcus Barnet

