通过HIDDEV编程接口读取易方数码笔的坐标数据

本文详细介绍了如何在Android平板电脑上通过将易方数码笔切换至hiddev设备类型,从而提高笔迹精度的方法。包括设备的打开、配置信息获取、数据读写等步骤,以及如何通过特定命令实现笔迹信息的原始坐标输出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

易方数码笔缺省是一个标准的USB HID设备(Mouse),在OS为Android的平板电脑上接入时,基于USB Mouse形式输出的笔迹精度不够,为此,我们将其切换至hiddev设备类型,通过发送相关命令来获得笔输出的原始坐标信息,这样精度就大大提升了。

基于USB HID协议,除可实现为普通的输入设备外,也可用作为有自定义通讯功能的设备。事实上,当前Linux 内核中已提供了这两种HID事件的接口,即输入子系统和hiddev接口(具体请参见Linux内核根目录下的Documentation/hid/hiddev.txt文档)。

为使用hiddev设备,我们只需在配置内核时要打开hiddev选项,这样就可以编写应用级代码来读取笔的原始坐标信息了。hiddev驱动是一个字符型驱动,其访问节点一般为/dev/usb/hiddev[0~15],在应用程序中打开这个设备节点后,即可调用hiddev API来与hiddev设备进行通讯。

hiddev API有两个调用接口,read和ioctl调用。read只用于获取hiddev设备的状态变化,而主机与设备间进行数据交换是通过ioctl调用来实现的,写数据时传入ioctl的命令字为HDIOCSREPORT,读数据时则传入HDIOCGREPORT,传送的数据封装在report中,每个report分成多个filed,而每个filed又有多个usage。

访问数码笔时,我们要向设备发命令数据来通知设备切换输出模式(输出原始坐标信息),同时,我们也要实时地读取出数据笔输出的原始坐标信息。

1. 打开设备

int digitalpen_open(void)
{
  int  index;
  int  fd;
  char hid_dev_node[50];
  struct hiddev_devinfo dinfo;
 
  for(index = 0; index < 15; index ++) {
 
    sprintf(hid_dev_node, "/dev/usb/hiddev%d", index);
    fd = open(hid_dev_node, O_RDONLY);
 
    if(fd > 0) {
 
      memset(&dinfo, 0, sizeof(dinfo));
      ioctl(fd, HIDIOCGDEVINFO, &dinfo);
 
      if( (dinfo.vendor == 0x0e20) && (dinfo.product == 0x0101))
        break;
 
      close(fd);
      fd = -1;
 
    }
  }
 
  return fd;
}

打开函数中通过对USB的VID和PID信息来确认所打开的设备是否为数码笔(可通过以下命令查到设备的VID和PID)。如打开成功,返回的是数码笔设备的文件描述符。

hfhe@hfhe:~$ lsusb
Bus 003 Device 003: ID 0e20:0101 Pegasus Technologies Ltd. NoteTaker
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 008: ID 04b3:3107 IBM Corp. ThinkPad 800dpi Optical Travel Mouse
Bus 001 Device 006: ID 04f2:b217 Chicony Electronics Co., Ltd
Bus 001 Device 005: ID 0a5c:217f Broadcom Corp. Bluetooth Controller
Bus 001 Device 004: ID 147e:2016 Upek Biometric Touchchip/Touchstrip Fingerprint Sensor
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

2. 获得数码笔的配置信息

在正式读写数据前,先获得数码笔设备的配置信息。我从Google上没有搜到较具体描述获知hiddev设备配置信息的文档,只能看Kernel中的代码和前面提及到的hiddev.txt文件,在此将与此相关的IOCTL命令整理如下:

  • HIDIOCGNAME – 获得设备名称
  • HIDIOCGDEVINFO – 获得由结构体hiddev_devinfo描述的设备信息
  • HIDIOCGCOLLECTIONINFO – 获得由结构体hiddev_collection_info描述的设备所拥有的集合(包括应用集合),调用时需传入index域,如index超过最后一 个集合,返回值为-1。
  • HIDIOCGREPORTINFO – 调用时需填写结构体hiddev_report_info中的type和id域。当ID为HID_REPORT_ID_FIRST传回第一个报告信息,(HID_REPORT_ID_NEXT |report_id)传回下一个报告信息;而类型值在HID_REPORT_TYPE_MIN和HID_REPORT_TYPE_MAX间。
  • HIDIOCGFIELDINFO – 返回报告中的域(filed)信息。调用时需传入报告的id和type, 也要填入field index,filed的数目从调用 HIDIOCGREPORTINFO的返回结构体中获得。
  • HIDIOCGUCODE – 返回用途码,调用时需传入报告的id和type、域的index和用途的索引(最大用途索引值在调用HIDIOCGFIELDINFO的返回结构体中获得)

可见,除HIDIOCGNAME和HIDIOCGDEVINFO调用外,其他的均是枚举过程。相关结构体的定义就不附上了,下面直接给出获得配置信息的代码。

int digitalpen_getconfiginfo(int fd)
{
  char name[1024];
  int  index;
  int  report_type,report_id,field_index,usage_index;
 
  struct hiddev_devinfo dinfo;
  struct hiddev_collection_info cinfo;
  struct hiddev_report_info rinfo;
  struct hiddev_field_info finfo;
  struct hiddev_usage_ref uref;
 
  const char *collection_type_name[] = {"Physical", "Application", "Logical"};
  const char *report_type_name[] = {"Input", "Output", "Feature"};
 
  // get device name
  memset(name, 0, sizeof(name));
  if( ioctl(fd, HIDIOCGNAME(sizeof(name)), name) < 0) {;
    return -1;
  }
 
  // get device info
  memset(&dinfo, 0, sizeof(dinfo));
  if( ioctl(fd, HIDIOCGDEVINFO, &dinfo) < 0) {
    return -1;
  }
 
  printf("\n[Device Information]\n");
  printf("device name      = %s\n", name);
  printf("vendor id        = 0x%04x\n", dinfo.vendor & 0xffff);
  printf("product id       = 0x%04x\n", dinfo.product & 0xffff);
  printf("version          = 0x%04x\n", dinfo.version & 0xffff);
  printf("num_applications = 0x%x\n",   dinfo.num_applications);
 
  // get collection info
  printf("\n[Collections Information]\n");
 
  index = 0;
  while(1) {
    memset(&cinfo, 0, sizeof(cinfo));
    cinfo.index = index;
 
    if(ioctl(fd, HIDIOCGCOLLECTIONINFO, &cinfo) < 0)
      break;
    index++;
 
    printf("index = %d\n", cinfo.index);
    if(cinfo.type >= 0 && cinfo.type <= 2)
      printf("type  = %s\n", collection_type_name[cinfo.type]);
    else
      printf("type  = %d\n", cinfo.type);
    printf("usage = 0x%x\n", cinfo.usage);
    printf("level = %d\n\n", cinfo.level);
  }
 
  // get reports info
  printf("[Report Information]\n");
  for(report_type = HID_REPORT_TYPE_MIN; report_type <= HID_REPORT_TYPE_MAX; report_type++) {
 
    for(report_id = HID_REPORT_ID_FIRST; ;report_id++) {
      memset(&rinfo, 0, sizeof(rinfo));
      rinfo.report_type = report_type;
      rinfo.report_id = report_id;
      if(ioctl(fd, HIDIOCGREPORTINFO, &rinfo) < 0)
        break;
 
      printf("report_type = %s\n", report_type_name[report_type - 1]);
      printf("report_id   = %d\n", report_id);
      printf("num_fields  = %d\n", rinfo.num_fields);
 
      // get field info
      printf("\n    [field information]\n");
      for(field_index = 0; field_index < rinfo.num_fields; field_index++) {
        memset(&finfo, 0, sizeof(finfo));
        finfo.report_type = report_type;
        finfo.report_id   = report_id;
        finfo.field_index = field_index;
 
        if(ioctl(fd, HIDIOCGFIELDINFO, &finfo) < 0)
          break;
 
        printf("    field_index   = %d\n",   finfo.field_index);
        printf("    maxusage      = %d\n",   finfo.maxusage);
        printf("    flags         = %d\n",   finfo.flags);
        printf("    physical      = %d\n",   finfo.physical);
        printf("    logical       = %d\n",   finfo.logical);
        printf("    application   = 0x%x\n", finfo.application);
        printf("    unit_exponent = %d\n",   finfo.unit_exponent);
        printf("    unit          = %d\n",   finfo.unit);
        printf("    logical_minimum  = %d; logical_maximum  = %d\n", finfo.logical_minimum, finfo.logical_maximum);
        printf("    physical_minimum = %d; physical_maximum = %d\n", finfo.physical_minimum, finfo.physical_maximum);
 
        // get usage info
        printf("\n        [Usage code information]\n");
        for(usage_index = 0; usage_index < finfo.maxusage; usage_index++) {
          memset(&uref, 0, sizeof(uref));
          uref.report_type = report_type;
          uref.report_id   = report_id;
          uref.field_index = field_index;
          uref.usage_index = usage_index;
          if(ioctl(fd, HIDIOCGUCODE, &uref) < 0)
            break;
 
          printf("        usage_index = %d, usage_code = 0x%x, value = %d\n",  uref.usage_index, uref.usage_code, uref.value);
        }
      }
 
      report_id = report_id | HID_REPORT_ID_NEXT;
      printf("\n");
 
    }
  }
}

运行后输出结果为:

[Device Information]
device name      = Pegasus Technologies Ltd. EN202 Ver 3.02
vendor id        = 0x0e20
product id       = 0x0101
version          = 0x0303
num_applications = 0x1
 
[Collections Information]
index = 0
type  = Application
usage = 0xffa00001
level = 0
 
[Report Information]
report_type = Input
report_id   = 256
num_fields  = 1
 
    [field information]
    field_index   = 0
    maxusage      = 64
    flags         = 2
    physical      = 0
    logical       = 0
    application   = 0xffa00001
    unit_exponent = 0
    unit          = 0
    logical_minimum  = -128; logical_maximum  = 127
    physical_minimum = 0; physical_maximum = 0
 
        [Usage code information]
        usage_index = 0, usage_code = 0xffa00002, value = 0
        usage_index = 1, usage_code = 0xffa00002, value = 0
        usage_index = 2, usage_code = 0xffa00002, value = 0
        usage_index = 3, usage_code = 0xffa00002, value = 0
        usage_index = 4, usage_code = 0xffa00002, value = 0
        usage_index = 5, usage_code = 0xffa00002, value = 0
        usage_index = 6, usage_code = 0xffa00002, value = 0
        usage_index = 7, usage_code = 0xffa00002, value = 0
        usage_index = 8, usage_code = 0xffa00002, value = 0
        usage_index = 9, usage_code = 0xffa00002, value = 0
        usage_index = 10, usage_code = 0xffa00002, value = 0
        usage_index = 11, usage_code = 0xffa00002, value = 0
        usage_index = 12, usage_code = 0xffa00002, value = 0
        usage_index = 13, usage_code = 0xffa00002, value = 0
        usage_index = 14, usage_code = 0xffa00002, value = 0
        usage_index = 15, usage_code = 0xffa00002, value = 0
        usage_index = 16, usage_code = 0xffa00002, value = 0
        usage_index = 17, usage_code = 0xffa00002, value = 0
        usage_index = 18, usage_code = 0xffa00002, value = 0
        usage_index = 19, usage_code = 0xffa00002, value = 0
        usage_index = 20, usage_code = 0xffa00002, value = 0
        usage_index = 21, usage_code = 0xffa00002, value = 0
        usage_index = 22, usage_code = 0xffa00002, value = 0
        usage_index = 23, usage_code = 0xffa00002, value = 0
        usage_index = 24, usage_code = 0xffa00002, value = 0
        usage_index = 25, usage_code = 0xffa00002, value = 0
        usage_index = 26, usage_code = 0xffa00002, value = 0
        usage_index = 27, usage_code = 0xffa00002, value = 0
        usage_index = 28, usage_code = 0xffa00002, value = 0
        usage_index = 29, usage_code = 0xffa00002, value = 0
        usage_index = 30, usage_code = 0xffa00002, value = 0
        usage_index = 31, usage_code = 0xffa00002, value = 0
        usage_index = 32, usage_code = 0xffa00002, value = 0
        usage_index = 33, usage_code = 0xffa00002, value = 0
        usage_index = 34, usage_code = 0xffa00002, value = 0
        usage_index = 35, usage_code = 0xffa00002, value = 0
        usage_index = 36, usage_code = 0xffa00002, value = 0
        usage_index = 37, usage_code = 0xffa00002, value = 0
        usage_index = 38, usage_code = 0xffa00002, value = 0
        usage_index = 39, usage_code = 0xffa00002, value = 0
        usage_index = 40, usage_code = 0xffa00002, value = 0
        usage_index = 41, usage_code = 0xffa00002, value = 0
        usage_index = 42, usage_code = 0xffa00002, value = 0
        usage_index = 43, usage_code = 0xffa00002, value = 0
        usage_index = 44, usage_code = 0xffa00002, value = 0
        usage_index = 45, usage_code = 0xffa00002, value = 0
        usage_index = 46, usage_code = 0xffa00002, value = 0
        usage_index = 47, usage_code = 0xffa00002, value = 0
        usage_index = 48, usage_code = 0xffa00002, value = 0
        usage_index = 49, usage_code = 0xffa00002, value = 0
        usage_index = 50, usage_code = 0xffa00002, value = 0
        usage_index = 51, usage_code = 0xffa00002, value = 0
        usage_index = 52, usage_code = 0xffa00002, value = 0
        usage_index = 53, usage_code = 0xffa00002, value = 0
        usage_index = 54, usage_code = 0xffa00002, value = 0
        usage_index = 55, usage_code = 0xffa00002, value = 0
        usage_index = 56, usage_code = 0xffa00002, value = 0
        usage_index = 57, usage_code = 0xffa00002, value = 0
        usage_index = 58, usage_code = 0xffa00002, value = 0
        usage_index = 59, usage_code = 0xffa00002, value = 0
        usage_index = 60, usage_code = 0xffa00002, value = 0
        usage_index = 61, usage_code = 0xffa00002, value = 0
        usage_index = 62, usage_code = 0xffa00002, value = 0
        usage_index = 63, usage_code = 0xffa00002, value = 0
 
report_type = Output
report_id   = 256
num_fields  = 1
 
    [field information]
    field_index   = 0
    maxusage      = 8
    flags         = 8
    physical      = 0
    logical       = 0
    application   = 0xffa00001
    unit_exponent = 0
    unit          = 0
    logical_minimum  = -128; logical_maximum  = 127
    physical_minimum = 0; physical_maximum = 255
 
        [Usage code information]
        usage_index = 0, usage_code = 0xffa00003, value = 0
        usage_index = 1, usage_code = 0xffa00003, value = 0
        usage_index = 2, usage_code = 0xffa00003, value = 0
        usage_index = 3, usage_code = 0xffa00003, value = 0
        usage_index = 4, usage_code = 0xffa00003, value = 0
        usage_index = 5, usage_code = 0xffa00003, value = 0
        usage_index = 6, usage_code = 0xffa00003, value = 0
        usage_index = 7, usage_code = 0xffa00003, value = 0

3. 向数码笔设备写入数据

与hiddev设备进行数据交换不是通过read和write这两个函数来完成的,而是通过调用IOCTL(命令字为HIDIOCGREPORT和HIDIOCSREPORT)并将传入传出的数据封装在report中来实现的。

写数据比较容易实现,根据hiddev.txt中的描述,就是在发出报告之间先通过调用HIDIOCSUSAGE(传给IOCTL)来填写将要传送的数据,其后调用HIDIOCSREPORT 让Linux内核发报告给设备。

int hiddev_send_report(int fd, unsigned int report_id, unsigned int num_fields, unsigned int field_index, unsigned int usage_index, unsigned int usage_code, char *buf, unsigned int size)
{
  unsigned int index;
  struct hiddev_usage_ref_multi urefm;
  struct hiddev_report_info rinfo;
 
  if(size > HID_MAX_MULTI_USAGES)
    return -1;
 
  memset(&urefm, 0, sizeof(urefm));
  urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT;
  urefm.uref.report_id = report_id;
  urefm.uref.field_index = field_index;
  urefm.uref.usage_index = usage_index;
  urefm.uref.usage_code = usage_code;
 
  urefm.num_values = size;
  for(index = 0; index < size; index++)
    urefm.values[index] = (unsigned int)buf[index];
 
  if(ioctl(fd, HIDIOCSUSAGES, &urefm) < 0)
    return -2;
 
  memset(&rinfo, 0, sizeof(rinfo));
  rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
  rinfo.report_id = report_id;
  rinfo.num_fields = num_fields;
  if(ioctl(fd, HIDIOCSREPORT, &rinfo) < 0)
    return -3;
 
  return 0;
}

调用时,要用到2小节中获取的一些参数,比较好的作法是将这些参数放进全司结构体中。当然,为图方便也可直接填入获取到的参数,例如要将数码笔切换至原始坐标输出模式,通过下面的调用即实现。

const char cmd_opmode_pen[]   = { 0x02, 0x04, 0x80, 0xB5, 0x01, 0x01, 0x00, 0x00};
 
if(hiddev_send_report(fd, HID_REPORT_ID_FIRST, 1, 0, 0, 0xffa00003, cmd_opmode_pen, sizeof(cmd_opmode_pen)) < 0)
    printf("Fail to send report\n");

通过所获得的配置信息可知,报告类型为输出模式时,report_id的值为256,即为宏HID_REPORT_ID_FIRST所定义的值。

<think>我们正在配置PVE系统使用NUT实现UPS断电自动保护。根据引用[1]和[2],我们可以知道NUT(Network UPS Tools)是一个用于管理不间断电源(UPS)的工具,它可以在断电时安全地关闭系统。在PVE(Proxmox VE)中配置NUT,通常有两种模式:将PVE作为NUT客户端(连接到另一台作为NUT服务器的设备)或者将PVE自身作为NUT服务器(如果UPS直接连接到PVE主机)。 但是,引用[2]提到,很多教程中群晖作为UPS主机,其他设备作为副机。然而,如果我们将PVE作为宿主系统,并且虚拟了群晖,通常需要将USB直通给群晖,这样群晖就可以直接管理UPS。但是这样会导致宿主系统(PVE)无法直接获取UPS状态。因此,我们需要考虑另一种方案:将UPS连接到PVE主机,然后配置PVE作为NUT服务器,这样虚拟机和局域网内的其他设备都可以作为客户端连接到PVE的NUT服务。 根据用户的问题,我们将在PVE系统中配置NUT,使其作为NUT服务器(如果UPS直接连接到PVE)或者作为客户端(如果UPS连接到网络中的另一台NUT服务器)。这里我们假设用户希望将PVE配置为NUT服务器(因为问题中要求PVE系统实现断电自动保护)。 步骤: 1. 安装NUT软件包 2. 配置NUT(主要配置文件:/etc/nut/) 3. 设置NUT服务模式(通常为standalone或netserver) 4. 配置UPS驱动 5. 配置用户权限和监控设置 6. 启动服务并测试 具体步骤: 第一步:安装NUT 在PVE命令行中执行: ```bash apt update apt install nut ``` 第二步:配置NUT 主要配置文件在/etc/nut/目录下,包括: - nut.conf:设置运行模式 - ups.conf:配置UPS设备 - upsd.conf:配置upsd服务 - upsd.users:配置用户 - upsmon.conf:配置监控 我们首先设置运行模式(nut.conf): 如果PVE作为NUT服务器,则设置: ```bash MODE=standalone # 或者netserver,表示作为网络服务器运行 ``` 或者 ```bash MODE=netserver ``` 第三步:配置UPS设备(ups.conf) 假设UPS通过USB连接到PVE主机,我们需要确定UPS的驱动和连接方式。可以使用`nut-scanner`来帮助识别: ```bash nut-scanner ``` 或者查看支持的驱动列表:http://networkupstools.org/stable-hcl.html 假设我们使用的UPS是APC的,驱动为usbhid-ups,设备节点为/dev/usb/hiddev0(具体设备节点可以通过lsusb或者查看dmesg日志确定)。在ups.conf中添加: ``` [ups] driver = usbhid-ups port = auto # 或者指定具体的设备节点,如/dev/hiddev0 desc = "My UPS" ``` 注意:不同的UPS可能需要不同的驱动,请根据实际情况配置。 第四步:配置upsd服务(upsd.conf和upsd.users) 默认情况下,upsd.conf可以保持默认,它主要设置监听地址等。如果需要绑定到特定地址,可以修改。 然后配置upsd.users,添加一个用户用于监控。例如,添加一个用户名为`monuser`,密码为`secret`,并赋予监控权限: ``` [monuser] password = secret upsmon master # 或者设置为slave,这里作为服务器,所以用master ``` 第五步:配置监控(upsmon.conf) 我们需要配置upsmon来监控UPS状态,并在断电时执行关机操作。编辑/etc/nut/upsmon.conf: ``` MONITOR ups@localhost 1 monuser secret master ``` 解释:监控本地UPS(名为ups,在ups.conf中定义),使用用户monuser和密码secret,模式为master(因为这是直接连接UPS的主机)。 然后设置关机命令,默认已经有一个SHUTDOWNCMD,但我们可以检查一下: ``` SHUTDOWNCMD "/sbin/shutdown -h +0" ``` 还可以设置断电后等待的时间(在电池电量低时关机)等参数。 第六步:启动服务并设置开机自启 ```bash systemctl enable nut-server nut-client nut-monitor systemctl start nut-server nut-client nut-monitor ``` 注意:在Debian(PVE的基础系统)中,服务名称可能为nut-server(对应upsd)、nut-client(对应upsdrv)和nut-monitor(对应upsmon)。但根据安装情况,可能只需要启动nut-driver(如果作为服务器)和nut-server,以及nut-monitor。 实际上,在standalone模式下,通常启动: ```bash systemctl start nut-driver # 这个会启动驱动(ups.conf中配置的驱动) systemctl start nut-server # 启动upsd systemctl start nut-monitor # 启动监控 ``` 第七步:测试 我们可以通过以下命令检查UPS状态: ```bash upsc ups@localhost ``` 模拟断电(拔掉UPS的电源插头),观察日志(/var/log/messages 或 journalctl -u nut-monitor)看是否检测到断电并触发关机。 另外,还可以使用命令测试关机(注意:这会导致系统关机,请谨慎): ```bash upsmon -c fsd ``` 该命令会触发UPSmon执行紧急关机(因为发送了FSD信号)。 引用[1]中提到了一些配置文件,我们可以参考其中的说明。另外,引用[2]提到了一种情况,即如果UPS连接到虚拟机(比如群晖),那么宿主系统就无法直接管理UPS。所以,如果用户的情况是UPS直接连接到PVE主机,那么按照上述步骤配置即可;如果UPS连接到虚拟机,那么需要将虚拟机配置为NUT服务器,然后在PVE中配置为NUT客户端(副机)。但是,用户的问题是在PVE系统中配置,所以我们假设UPS直接连接到PVE主机。 如果用户希望将PVE配置为NUT客户端(连接到另一台NUT服务器),则配置方式有所不同: 1. 在PVE上安装nut-client(通常安装nut包已经包含) 2. 配置nut.conf为netclient 3. 在upsmon.conf中配置MONITOR指令,指向NUT服务器,并设置为slave模式 例如: ``` MONITOR ups@nut-server-hostname 1 remoteuser remotepass slave ``` 然后启动nut-monitor服务。 但是,由于用户要求的是在PVE系统中实现断电自动保护,所以我们重点放在将PVE作为NUT服务器的配置上。 注意:在配置过程中,确保防火墙允许NUT使用的端口(默认是TCP和UDP的3493)。 最后,生成相关问题。</think>要在PVE系统中配置NUT实现UPS断电自动保护,请按以下步骤操作: ### 一、基础环境准备 1. **安装NUT软件包** ```bash apt update && apt install nut nut-client nut-server ``` 2. **连接UPS设备** - 通过USB连接UPS到PVE主机 - 确认设备节点(通常为`/dev/usb/hiddev0`或`/dev/hidraw0`) ```bash ls /dev | grep -i hid ``` ### 二、核心配置文件设置 1. **`/etc/nut/nut.conf`**(运行模式) ```conf MODE=netserver # 作为主服务器运行 ``` 2. **`/etc/nut/ups.conf`**(UPS设备定义) ```conf [myups] # UPS自定义名称 driver = usbhid-ups port = auto # 自动检测USB端口 desc = "APC SmartUPS 1500" # 若需指定设备:port = /dev/hidraw0 ``` > 📌 驱动名查询:访问[NUT兼容列表](http://networkupstools.org/stable-hcl.html) 3. **`/etc/nut/upsd.users`**(用户权限) ```conf [monuser] # 监控专用账户 password = your_secure_password upsmon master # 主控节点权限 ``` 4. **`/etc/nut/upsmon.conf`**(监控配置) ```conf MONITOR myups@localhost 1 monuser your_secure_password master SHUTDOWNCMD "/usr/sbin/pvectl safe-shutdown-all" # PVE安全关机命令 POWERDOWNFLAG /etc/killpower # 断电标志文件 ``` ### 三、服务启动与测试 ```bash systemctl restart nut-driver nut-server nut-monitor systemctl enable nut-driver nut-server nut-monitor ``` **验证配置**: ```bash # 检查UPS状态 upsc myups@localhost # 测试通信(返回PONG表示正常) upscmd -u monuser -p your_secure_password myups@localhost test.battery.start ``` ### 四、断电模拟测试 1. 拔掉UPS电源插头 2. 查看日志确认触发动作: ```bash tail -f /var/log/messages | grep upsmon ``` 3. 应观察到以下流程: ```mermaid sequenceDiagram UPS->>NUT: 断电警报 NUT->>PVE: 发送关机指令 PVE->>虚拟机: 依次安全关闭 PVE->>物理机: 最后关闭主机 ``` ### 五、高级配置(可选) 1. **网络客户端接入**(其他设备保护) 在客户端设备的`upsmon.conf`中添加: ```conf MONITOR myups@192.168.1.10 1 monuser your_secure_password slave ``` 2. **延迟关机设置** ```conf # /etc/nut/upsmon.conf MINSUPPLIES 1 # 至少1个电源在线 SHUTDOWNDELAY 300 # 断电5分钟后关机 ``` > ⚠️ **关键注意事项** > 1. 若使用USB直通给虚拟机(如群晖),需在PVE配置USB Passthrough > 2. 企业级环境建议配置冗余监控:`NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC`[^1] > 3. 完整测试周期:每季度模拟断电测试一次 ### 六、故障排查 ```bash # 查看驱动状态 upsdrvctl -D start # 检查服务状态 nut-scanner -U systemctl status nut-* ``` --- **相关问题**: 1. 如何将群晖配置为NUT客户端连接到PVE的UPS服务? 2. PVE安全关机命令`safe-shutdown-all`的具体工作流程是什么? 3. 多节点集群环境下如何配置NUT实现高可用? [^1]: NUT官方配置手册 [^2]: UPS与虚拟化平台联动实践
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值