前言

HP 1020 Plus 属于 GDI 驱动(基于主机的驱动),此类打印机需要连接上主机后,由主机主动向打印机写入一段驱动才可以正常运行。

而威联通是不支持此类打印机的。不支持体现在打印机可以正常连接上 NAS,但是点击打印后,显示打印完成,但是没有产生任何实际打印行为。

在网上搜到一篇此类问题的求助:QNAP USB打印机问题

问题

上述链接中,有人回答使用虚拟机,但是用虚拟机又带来了几个问题:

  1. Windows 可以很好的支持 GDI 驱动打印机,但是 Linux 系列比较麻烦,需要手动安装驱动。我虚拟机中运行的是 Ubuntu Server 18;
  2. 每次 USB 打印机连接上 NAS 之后,必须手动的在 Virtualization Station 中将该 USB 设备分配给虚拟机,非常麻烦,毕竟打印机不会常开,我家人并不会操作威联通的管理页面;

解决问题

1. 给 Ubuntu Server 安装驱动

首先解决第一个问题,Ubuntu Server 如何安装 HP 1020 Plus 的驱动?请参考我的这篇博客:。

我在文中详细的描述了以下几件事情:

  1. 安装 cups 用于管理打印机;
  2. 利用 foo2zjs 安装打印机驱动,并将驱动设置到 cups 中;

2. 自动分配 USB 打印机给虚拟机

  1. 检测虚拟机是否有 USB 打印机;
  2. 如果虚拟机没有 USB 打印机则将 USB 打印机分配给虚拟机;

这一步可以通过脚本完成,先解释一下脚本涉及到的几个知识点:

2.1. virsh 相关知识
  1. virsh 是管理 KVM 虚拟机的工具,也是 VirtualizationStation 的虚拟机管理工具。利用 virsh 工具我们可以检测虚拟机分配了哪些 USB 设备,也可以将 USB 设备分配给虚拟机;
  2. virsh 命令没有被加到环境变量,所以需用绝对路径:/QVS/usr/bin/virsh
2.2. 通过 virsh 获取虚拟机名称

虚拟机名称可以通过 virsh list 命令获得,如:

[/QVS/usr/bin] $ /QVS/usr/bin/virsh list
 Id    Name                           State
----------------------------------------------------
 1     39d664db-d642-4922-9ddd-ccca6b372b9d running
2.3. 创建 USB 设备的 XML 描述文件

先看下我的 XML 文件:

<hostdev mode='subsystem' type='usb'>
    <source>
        <vendor id='0x03f0'/>
        <product id='0x2b17'/>
    </source>
</hostdev>

这里有 2 个点需要注意,vendor id 和 product id,每个设备都具有唯一的两个 ID,如何获取呢?通过 lsusb 命令,如:

[/QVS/usr/bin] $ lsusb
Bus 001 Device 003: ID 1005:b155 Apacer Technology, Inc. 
Bus 001 Device 008: ID 03f0:2b17 Hewlett-Packard LaserJet 1020 # 这一行是我的 HP 1020 Plus 打印机
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub

注意到 03f0:2b17 了吗?03f0 就是 vendor id,2b17 就是 product id。

2.4. 创建 shell 脚本检测并分配设备给虚拟机

脚本文件如下:

#!/bin/bash
# set -x
set -euo pipefail

# 宿主机是否连接设备
HOST_ATTACHED=0
# 虚拟机是否已连接设备
VM_ATTACHED=0
# 虚拟机名称
NAME="39d664db-d642-4922-9ddd-ccca6b372b9d"
# USB 打印机的 XML 描述文件
XML_PATH="/share/my_cron/printer_1020_plus.xml"
# 本脚本工作的日志路径
LOG_PATH="/share/my_cron/printer_1020_plus_attach.log"
# USB 设备名称
DEVICE_NAME_FRAGMENT="LaserJet\ 1020"

if /QVS/usr/bin/virsh qemu-monitor-command --domain $NAME --hmp --cmd "info usb" | grep -q "$DEVICE_NAME_FRAGMENT"; then
	VM_ATTACHED=1
fi
if lsusb | grep -q "$DEVICE_NAME_FRAGMENT"; then
	HOST_ATTACHED=1
fi

# 如果虚拟机没有连接设备,且宿主机连接了设备,则将设备分配给虚拟机
if [ $VM_ATTACHED -eq 0 ] && [ $HOST_ATTACHED -eq 1 ]; then
	/QVS/usr/bin/virsh attach-device $NAME $XML_PATH > /dev/null
	MSG="`date` attached_printer"
	echo $MSG >> $LOG_PATH
fi

# 虚拟机连接了设备,但是宿主机没有该设备,则意味着设备是通过断电离线的,需要排除
# 此场景只出现了一次,暂不能确定具体原因
if [ $VM_ATTACHED -eq 1 ] && [ $HOST_ATTACHED -eq 0 ]; then
	/QVS/usr/bin/virsh detach-device $NAME $XML_PATH > /dev/null
	MSG="`date` detached_printer"
	echo $MSG >> $LOG_PATH
fi

注意将脚本中的变量修改为你自己的,变量我都放置在脚本前面了,有注释的那 4 个。

2.5. 创建定时任务

另有一种方案:利用 udev 监听 usb 设备变化,无需定时任务,参考:https://github.com/Bpazy/usb-libvirt-hotplug

首先修改 cron 配置:

# 每分钟运行一次脚本,检测到 USB 设备没有分配给虚拟机则会进行分配
echo "*/1 * * * * /bin/bash /share/my_cron/printer_1020_plus_attacher.sh" >> /etc/config/crontab
# 重启 crontab
crontab /etc/config/crontab && /etc/init.d/crond.sh restart

完结撒花

好了,这下可以放心的给打印机断电了,需要的时候打开打印机,会自动分配给虚拟机,而后就可以正常打印了。

还有个问题,就是通过 virsh 分配 USB 设备给虚拟机之后,在 VirtualizationStation 里面体现不出来,会显示 USB 仍未分配给虚拟机,不过这个显然是个小问题,忽略即可。

最后看个日志吧

Thu Sep 10 17:15:00 HKT 2020 attached_printer
Thu Sep 10 19:03:00 HKT 2020 detached_printer
Thu Sep 10 19:07:00 HKT 2020 attached_printer
Thu Sep 10 19:41:00 HKT 2020 detached_printer
Thu Sep 10 22:06:05 HKT 2020 attached_printer
Fri Sep 11 00:08:00 HKT 2020 detached_printer