正点原子linux 使用QT 自动检测u盘插拔并寻找特定文件

目录

  • 正点原子开发板Alpha配置环境需求
  • linux自动挂载u盘
    • 使用udev自动挂载u盘
      • mount.sh代码解析
    • 查看u盘挂载情况
    • 使用QT检测u盘插入
      • 1.增加头文件libudev.h
      • 2.在.pro增加QT application对libudev库的链接
      • 3.在头文件.h里新建udev结构体指针并在构造函数里初始化udev
      • 4.创建udev监视器并用QT槽连接处理函数
      • 5.在析构函数里增加对udev的释放
      • 6.编写u盘插入处理函数
    • 已插入U盘检测

笔者刚解决完这个问题,前期有些忘了,一点一点回忆。会慢慢更新。

正点原子开发板Alpha配置环境需求

笔者使用的是Alpha出厂系统,也就是用Yocto做的根文件系统。这个系统里包含了QT和很多其他必要的库,可以不用在开发板上再安装了。

linux自动挂载u盘

使用udev自动挂载u盘

正点原子提供的出厂系统中已经内置了用udev自动挂载u盘的脚本,在/etc/udev 文件夹下有个脚本文件夹scripts,里面有个自动挂载的脚本mount.sh,部分内容如下:

...

automount() {	
	name="`basename "$DEVNAME"`"

	! test -d "/mnt/$name" && mkdir -p "/mnt/$name"
	# Silent util-linux's version of mounting auto
	if [ "x`readlink $MOUNT`" = "x/bin/mount.util-linux" ] ;
	then
		MOUNT="$MOUNT -o silent"
	fi
	
	# If filesystem type is vfat, change the ownership group to 'disk', and
	# grant it with  w/r/x permissions.
	case $ID_FS_TYPE in
	vfat|fat)
		MOUNT="$MOUNT -o umask=007,gid=`awk -F':' '/^disk/{print $3}' /etc/group`"
		;;
	# TODO
	*)
		;;
	esac

	if ! $MOUNT -t auto $DEVNAME "/mnt/$name"
	then
		logger "mount.sh/automount" "$MOUNT -t auto $DEVNAME \"/mnt/$name\" failed!"
		rm_dir "/mnt/$name"
	else
		logger "mount.sh/automount" "Auto-mount of [/mnt/$name] successful"
		touch "/tmp/.automount-$name"
	fi
}	
...

mount.sh代码解析

basename 是是去掉文件明前面的目录并打印出来,比如/dev/sda1 打印出来就是sda1。
$DEVNAME是由udev提供的系统环境变量,表示正在处理的块设备的名称。这个系统环境变量可以通过输入命令udevadm test /sys/class/block/sda查看。

DEVLINKS=/dev/disk/by-id/usb-USB_Disk_3727637990721561-0:0 /dev/disk/by-path/platform-ci_hdrc.1-usb-0:1.4:1.0-scsi-0:0:0:0
DEVNAME=/dev/sda
DEVPATH=/devices/platform/soc/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb1/1-1/1-1.4/1-1.4:1.0/host2/target2:0:0/2:0:0:0/block/sda
DEVTYPE=disk

脚本里用到了这些命令:
test -d 文件名: 如果文件存在且为目录则为真。
mkdir -p 文件夹名: 确保目录名称存在,不存在的就建一个。
mount -o auto、-o noauto:打开/关闭自动挂上模式。

开机后会自动执行这个脚本,u盘插入就会挂上,挂载到/mnt下

查看u盘挂载情况

使用df -h命令或者mount能查看

root@ATK-IMX6U:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       120M  1.7M  119M   2% /mnt/sda1
root@ATK-IMX6U:~# mount
/dev/sda1 on /mnt/sda1 type vfat (rw,relatime,gid=6,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)

使用QT检测u盘插入

在QT工程中做如下改动

1.增加头文件libudev.h

#include 

确保系统上安装了libudev库。如果尚未安装,使用 Linux 发行版的包管理器进行安装。在Ubuntu 的系统上,使用以下命令进行安装:
sudo apt-get install libudev-dev

2.在.pro增加QT application对libudev库的链接

在.pro文件中增加一行 LIBS += -ludev

    udev = udev_new();
    if (!udev) {
        qFatal("Failed to initialize libudev.");
    }

    monitor = udev_monitor_new_from_netlink(udev, "udev");
    udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", NULL);
    udev_monitor_enable_receiving(monitor);
    int fd = udev_monitor_get_fd(monitor);
    QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
    connect(notifier, &QSocketNotifier::activated, this, &viva::usbDiskInserted);

通过添加-ludev到LIBS变量,编译程序将在链接过程中搜索库并解析对函数udev_new和其他libudev函数的引用。
确保系统上安装了libudev库。如果尚未安装,使用 Linux 发行版的包管理器进行安装。在Ubuntu 的系统上,使用以下命令进行安装:
sudo apt-get install libudev-dev

3.在头文件.h里新建udev结构体指针并在构造函数里初始化udev

struct udev *udev; 
udev = udev_new();
if (!udev) {
    qFatal("初始化libudev失败");
}

4.创建udev监视器并用QT槽连接处理函数

struct udev_monitor *monitor = udev_monitor_new_from_netlink(udev, "udev"); //创建一个udev对象来监听设备事件
udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", NULL); //指定设备子系统(如块设备的block)
udev_monitor_enable_receiving(monitor); //将监视器添加到事件循环中
int fd = udev_monitor_get_fd(monitor);
QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
connect(notifier, &QSocketNotifier::activated, this, &MyClass::usbDiskInserted); //连接信号槽

上面代码设置了一个监视器来过滤块设备并将其接收QSocketNotifier发来的通知并转发到usbDiskInserted

5.在析构函数里增加对udev的释放

养成良好习惯,避免内存泄漏

udev_monitor_unref(monitor);
udev_unref(udev);

6.编写u盘插入处理函数

这里设置成只要有u盘插入,查找并读取里面的target.txt文件

void mainwindow::usbDiskInserted()
{
    struct udev_device *dev = udev_monitor_receive_device(monitor);
    if (dev) {
        const char *action = udev_device_get_action(dev);
        if (action && strcmp(action, "add") == 0) { //如果是插入u盘执行下面
            const char *devpath = udev_device_get_devpath(dev);
            const char *devname = udev_device_get_devnode(dev);

            qDebug() << "u盘路径:" << devpath << "节点名称:" << devname;

            QString mountPoint = getUsbDiskMountPoint(devname); //返回设备节点名称获取挂载路径

            // 在u盘中寻找target.txt
            QString targetFile = searchFileInUsbDisk(mountPoint, "target.txt");
            if (!targetFile.isEmpty()) {
                qDebug() << "目标文件路径: " << targetFile;
                // 此处可以写对target.txt的处理
            } else {
                qDebug() << "未找到文件";
            }
        }
        else if(action && strcmp(action, "remove") == 0) //如果是拔出u盘执行下面
            qDebug() << "u盘已经拔出";

        udev_device_unref(dev); //用于释放对特定udev_device对象的引用
    }
}

其中获取挂载路径函数getUsbDiskMountPoint

QString mainwindow::getUsbDiskMountPoint(const QString &usbDiskPath)
{
    // 相当于在命令行输入mount命令获取u盘挂载路径
    QProcess process;
    process.start("mount");
    process.waitForFinished(-1);

    QByteArray output = process.readAllStandardOutput();
    QString mountOutput = QString::fromUtf8(output);

    // 读取mount on 后的字段,也就是挂载路径的文本
    QString searchString = usbDiskPath + " on ";  //相当于找这段文本/dev/sda1 on /mnt/sda1
    int index = mountOutput.indexOf(searchString);
    if (index != -1) {
        // 如果找到把on后面的文本/mnt/sda1提取出来
        QString mountPath = mountOutput.mid(index + searchString.length());
        mountPath = mountPath.left(mountPath.indexOf(' '));
        qDebug() << "挂载路径:" << mountPath;
        return mountPath;
    } else {
        qDebug() << "未找到路径";
        return QString();
    }
}

另一种获取挂载路径函数getUsbDiskMountPoint2,这个函数不用输入参数,更为简单,但有失败的可能

QString mainwindow::getUsbDiskMountPoint2()
{

    QString mountPath;

    foreach (const QStorageInfo &storage, QStorageInfo::mountedVolumes())
    {
        if (storage.isValid() && storage.isReady())
        {

            if (storage.rootPath().contains("sd")) {
                mountPath = storage.rootPath();
                qDebug() << "挂载路径:" << mountPath;
                return mountPath;
            }
            else {
            	qDebug() << "未找到路径";
            	return QString();
            }
            	
        } else {
            qDebug() << "未找到路径";
            return QString();
        }
    }

}

其中查找特定文件函数searchFileInUsbDisk

QString mainwindow::searchFileInUsbDisk(const QString &usbDiskPath, const QString &fileName)
{
	//使用QT遍历器,遍历读取u盘中的所有文件
    QDirIterator it(usbDiskPath, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
    while (it.hasNext()) {
        QString filePath = it.next();
        QFileInfo fileInfo(filePath);
        if (fileInfo.fileName() == fileName) {
            return fileInfo.filePath(); //找到返回文件路径
        }
    }

    return QString(); // 文件未找到
}

已插入U盘检测

上述情况是在进入App后插入u盘,触发QSocketNotifier找到u盘。
如果在进入App前就有u盘插入,可以使用上面的getUsbDiskMountPoint2()

你可能感兴趣的:(qt,linux,c++)