
设备科半夜接到电话CT室市电闪断UPS顶上去了可三分钟后电池电量掉到15%还没等值班工程师赶到现场设备已经因为电量耗尽强制关机。片子没出完患者多等了两小时科室主任的脸色比报告单还难看。事后复盘问题根本不在UPS本身而在“没人知道这台UPS的电池还能撑多久”。后来我基于NUTNetwork UPS Tools这套开源能源监测系统加上自写的采集程序和告警链路做了一套面向医疗设备供电场景的UPS实时监测系统源码和配置都整理成了开源项目挂在Gitee上。这篇文章把完整的设计思路、硬件选型、软件配置、部署记录和踩坑经验一次说清楚给同样被断电焦虑折磨的设备科同行一份可以直接参考的作业。1. 问题拆解医疗供电场景到底在怕什么1.1 医疗设备对供电的敏感点医院的供电线路并不是想象中“一马平川”的稳定电网。雷电、市政施工、变压器检修甚至同一栋楼里某台大功率空调压缩机启动瞬间拉低电压都会在市电波形上留下痕迹。对CT、MRI、DSA这类大型影像设备一次毫秒级的电压跌落就可能让球管曝光中断、扫描被迫重来。对ICU的呼吸机、监护仪、输液泵这些连续工作设备断电带来的风险不只是设备损坏更是治疗过程的中断——这种中断在某些场景下是分秒必争的。UPS存在的意义就是填补市电故障到备用发电机组启动之间那段真空期。我这些年巡检过不少科室发现一个普遍问题UPS安装完就没人管电池鼓包、内阻增大、容量衰减、整机处于旁路状态这些隐患平时完全看不出来一旦真断电就全部暴露。至少有三分之一的在用UPS没有任何远程监控手段电池状态对设备科来说就是个盲区。1.2 从“有UPS”到“管好UPS”的需求拆解动手做这套系统之前我先把需求列了个清单。不追求大而全只解决四个核心问题实时电量电池剩余电量百分比、预计后备时间、当前负载率这些关键参数必须随时可见。可预警剩余时间低于阈值时必须有人知道而不是等设备黑屏后才被电话吵醒。可追溯每一次断电事件的时间、时长、电池表现都要有日志方便事后复盘和电池健康评估。可联动对非生命支持类的服务器、存储设备要在安全关机窗口内做自动联动关机避免突然掉电损坏数据。这四个需求直接决定了技术选型的方向必须开源必须支持多种UPS通信协议必须方便二次开发对接。闭源商业监控软件我也看过价格倒不是最大问题真正的问题是医院设备科经常有定制化需求闭源方案改不动后期维护很被动。2. 开源方案选型为什么是NUT2.1 三种监测通道的对比很多同行一上来就想自己写Modbus协议解析结果被各家UPS厂家的私有协议劝退。其实UPS对外暴露的监测通道主要有三类选对了能省一半功夫。USB/HID通道现代中小型UPS最常见的接口。插上USB线系统识别成HID电源设备通过usbhid-ups驱动就能读取状态。优点是免费、即插即用绝大多数在线式UPS都支持。SNMP/网口通道大型UPS或机架式UPS常配网络管理卡。通过SNMP的OID可以读到输入输出电压、负载率、电池容量、剩余时间等几乎全部参数。好处是纯网络化不用近距离插线但网络管理卡通常是选配件价格不便宜。RS485/Modbus通道工业级或老旧型号UPS常用需要自己向厂家要协议文档数据最原始但解析工作量大。我的判断是中小型设备用USB通道大型设备用SNMP通道Modbus留给实在没有前两种接口的情况。这套系统在NUT层面把三种通道统一抽象成一套标准输出上层应用根本不用关心底层是哪种协议。2.2 NUT的核心价值与工作方式NUT是Linux生态里最成熟的开源UPS管理套件核心组件就三个upsd守护进程维护UPS状态数据库各类驱动如usbhid-ups、snmp-ups负责跟具体UPS硬件通信upsmon监控进程执行告警事件和关机策略。它的设计把“采集”和“策略”完全解耦。命令行下一条upsc就能随时抓取任意监控点的参数格式统一、简单直接。典型的输出是这样的$ upsc ct-upslocalhost battery.charge: 100 battery.runtime: 2400 battery.voltage: 27.4 input.voltage: 220.3 output.voltage: 219.8 ups.status: OL字段都是标准化的battery.charge是剩余电量百分比battery.runtime是预计后备秒数ups.status为OL表示在线OB表示正在电池供电。这套标准化字段就是整个项目的“数据底座”后面的采集程序、告警逻辑全部基于它开发。我当时敲定用NUT还有一个现实原因它支持几百种UPS型号社区驱动维护非常活跃冷门型号大概率也能找到现成驱动。让我自己从头写一个UPS的USB HID协议解析那工程量不是一般人能扛下来的。3. 系统架构与核心实现细节3.1 整体架构设计整个系统我分成四层数据采集层各科室的UPS通过USB或SNMP接入本地“探针盒”汇聚层探针盒运行NUT驱动把状态数据汇总到中心upsd服务器应用层一套Python采集服务定时轮询upsc并写入MySQL数据库同时把数据推给Grafana和告警模块执行层upsmon根据阈值触发安全关机脚本或经MQTT把消息推送到值班人员的手机。探针盒我建议用树莓派或二手瘦客户机配置要求不高。但有一个容易被忽视的细节探针盒本身也必须接在UPS供电回路上。监测系统自己要先断电了那所有告警都是空话。这个坑我踩过一次那次是设备间里临时插线把树莓派插在了普通墙插上结果市电一跳整个监控瞬间失联。3.2 核心配置与参数说明以一台ICU的3kVA在线式UPS为例它带USB口也带一块SNMP接口卡。我选择走USB通道理由是这种规模没必要占用网络管理卡USB通道足够稳定。树莓派上安装NUTsudo apt install nut nut-client nut-server然后需要配置三个文件。ups.conf告诉NUT硬件在哪、用什么驱动[icu-ups] driver usbhid-ups port auto desc ICU Powerupsd.conf配置监听地址和访问权限LISTEN 127.0.0.1 3493 LISTEN 192.168.10.20 3493upsmon.conf配置主从关系和关机策略MONITOR icu-upslocalhost 1 admin secret primary POLLFREQ 5 SHUTDOWNCMD /sbin/shutdown -h 0这里有个非常重要的经验MONITOR行最后的primary表示这台机器是主监控节点它负责在电池耗尽前执行关机。医疗场景下我强烈建议把upsmon的自动关机决策只用于服务器和存储设备绝对不能用于生命支持类设备。让机器自动切断一台正在运行的呼吸机电源这个责任没人敢背。正确的做法是保证告警到达人由人做最终判断。这也是整个系统设计上和普通机房监控最大的区别。3.3 电量与后备时间的二次估算逻辑NUT上报的battery.runtime是UPS固件估算的准确度的问题后面专门讲。我自己的做法是在应用层做了一次“二次估算”目的是交叉验证防止被单一数据源误导。核心思路是后备时间等于电池当前可用容量除以当前负载功率。可用容量从battery.charge百分比乘上电池组额定Wh得到负载功率用输出电压和输出电流实时计算也可以用UPS提供的outlet功率字段。每5秒算一次得到滑动平均值runtime_est battery_charge_pct * battery_rated_wh / load_power_w这个公式看着简单实际很能说明问题。NUT固件估算的是厂商基于新鲜电池的理想值而二次估算用的是实时负载和实时容量两者差值一旦超过30%基本可以断定电池容量已经明显衰减该安排放电测试或者准备换电池了。这套交叉验证机制帮我们提前发现过两台电池容量衰减超过40%的UPS都是在还没发生真实断电前处理掉的。3.4 三级告警链路与联动设计告警我设计了三级每级触发条件和动作都不同提示级市电异常UPS进入电池供电。只推送通知让值班人员知道当前状态。预警级剩余后备时间低于10分钟。推送值班人员同时点亮声光报警器。处置级剩余后备时间低于5分钟。触发服务器群安全关机脚本医疗设备端由人工决策是否启动应急转移。推送通道用的是企业微信机器人省去自建短信网关的麻烦。NUT本身支持NOTIFYCMD回调可以在事件发生的瞬间执行自定义脚本。一个实用技巧是事件脚本里只做“转发”把原始告警文本塞进MQTT再由单独的消息中台决定走企业微信、邮件还是短信。这样告警通道可以灵活调整不会因为某个第三方接口挂了就丢掉整条告警链路。4. 从零开始部署影像科三台设备的完整实录4.1 摸底与布线阶段我先整理了影像科三台UPS的通信接口情况CT室一台20kVA带SNMP管理卡DR室两台3kVA都是USB口。驱动方案很明确20kVA那台用snmp-ups驱动3kVA的两台用usbhid-ups驱动。探针盒选了两台树莓派4B一台放CT设备间一台放DR设备间。布线阶段有个大坑要提醒UPS随箱的USB线通常很短但设备间往往和值班室隔着几十米。我的做法是每台UPS附近放一台探针盒探针盒通过网线回传数据而不是试图把USB线延长十几米。USB延长线超过5米就会出现通信不稳定这是物理层的硬限制别指望靠驱动能救回来。4.2 驱动配置与首轮验证SNMP型号在ups.conf里这样写[ct-ups] driver snmp-ups port 192.168.10.50 community public snmp_version v2cUSB型号就写usbhid-upsport设成auto。配置完后启动服务sudo systemctl restart nut-server sudo systemctl restart nut-client upsc ct-upslocalhost我特别建议一定要先跑upsc手动验证逐项检查battery.charge、battery.runtime、ups.status这几个关键字段是否正常再进入上层应用的联调。很多问题其实在驱动阶段就能暴露不要急着写监控页面。我们当时就遇到一台老型号UPSusbhid-ups驱动读不到battery.runtime换了mge-usb驱动才正常。这种事情越早发现越省事。4.3 应用层采集服务与可视化看板Python采集服务用的是最简单的轮询模型import time import subprocess import pymysql def ups_status(name): out subprocess.check_output( [upsc, f{name}localhost] ).decode() d {} for line in out.splitlines(): k, v line.split(: , 1) d[k] v return d def save_to_db(u, data): # 写入MySQL按时间戳落库 pass while True: for u in [ct-ups, dr-ups1, dr-ups2]: data ups_status(u) save_to_db(u, data) time.sleep(5)这里用不着上什么高级框架subprocess调upsc拿到标准字段就是最稳定的接口。存库选了MySQL数据量完全没压力5秒一轮三台设备一天也就五万条记录。可视化用的是Grafana数据源直连MySQL。看板主要放五个面板电池剩余百分比、后备剩余时间、负载率、输入输出电压、最近断电事件列表。另外在临床科室护士站的大屏放一个简化版只显示“当前正常 / 电池供电中 / 剩余xx分钟”。信息越少越容易被值班人员消化这个设计原则我至今觉得是对的。4.4 联动脚本与应急演练做联动脚本前先定了一个紧急预案表层级触发条件动作三级市电中断电池供电推送告警声光报警器亮起二级剩余时间低于10分钟再次推送电话通知设备科值班一级剩余时间低于5分钟服务器群安全关机医疗设备由人工处置第一次演练我直接断开了市电输入观察系统表现5秒内弹出告警10秒内写入数据库30秒内值班手机收到推送。电池放电到50%时手动恢复市电整个流程算跑通。这种演练强烈建议每季度做一次而且要换人操作。真实断电发生时在现场的往往不是搭这套系统的人操作人越熟悉应急流程系统价值才越大。5. 常见问题与排查技巧实录5.1 USB通信频繁掉线这是出现频率最高的问题。现象是NUT日志里出现Device disconnectedupsc偶尔取不到数据。原因排序USB线质量差、驱动误判、树莓派供电不足。排查步骤换一根带磁环的粗USB线不要用随机短线的劣质替换品。检查树莓派电源5V/3A的适配器必须到位供电不足会导致外设反复枚举。用dmesg查看设备是否反复断开重连。我还用过一个小技巧把USB接口的自动挂起功能关掉在/etc/udev/rules.d/下新建规则ACTIONadd, SUBSYSTEMusb, TESTpower/control, ATTR{power/control}on这个规则对不少USB掉线问题立竿见影尤其是树莓派这类单板机上。5.2 电量百分比跳变现象是battery.charge从70%突然跳到99%或者反过来。原因是UPS固件采样算法的平滑度不够加上电池劣化后电压曲线非线性导致百分比出现台阶式跳变。处理办法是加“去抖”逻辑。我在采集服务里加了几行判断连续三次采样都在同一个区间才更新状态剩余时间低于10分钟的告警判断改为连续两轮均低于10分钟才触发。这样能有效避免单次数据异常导致的误告警。有一次凌晨三点告警把我叫醒起来一看UPS状态正常就是跳变触发的问题加了去抖之后再没发生过。5.3 battery.runtime虚高UPS显示剩余时间40分钟实际放电15分钟就关机了这是电池老化后最常见的坑。厂家估算基于新电池容量电池用了三年后实际容量可能只剩60%固件却还按新电池算。我的解决方案是在应用层维护一张电池健康度表每次做完整放电测试后回写一次实测后备时间。监控页面上优先展示实测校准值而不是固件原始值。校准值的获取方式做一次完整的放电测试从电池供电开始计时到低压报警或接近设定的放电下限为止记录实际分钟数和当时的平均负载。后续每次真实断电系统也会自动更新这张表展示的剩余时间会越来越准。5.4 常见问题速查表症状可能原因处理方式upsc能通但没有battery字段驱动型号不匹配换usbhid-ups或mge-usb驱动重新探测SNMP读不到数据community不匹配、端口被防火墙拦截检查UDP 161端口与public字符串upsmon频繁上报电池模式输入电压波动导致误触发调高输入电压预警阈值树莓派重启后NUT不自动启动服务未启用或依赖顺序错误systemctl enable nut-server确认网络服务就绪告警只发了一次后续没再发缺少去重和状态变化逻辑事件状态变化时才触发推送不要周期重发6. 开源改造与后续扩展6.1 源码结构说明我把整套系统的源码、配置文件、Grafana看板JSON都整理成了开源项目目录结构大致是medical-ups-monitor/ ├── nut/ # NUT配置示例 ├── collector/ # Python采集服务 ├── scripts/ # 联动与关机脚本 ├── dashboard/ # Grafana看板JSON └── docs/ # 部署文档与拓扑图开源的价值在于医院设备科普遍人手紧张没人有精力从零写一套协议解析和采集框架。基于NUT这个成熟底座我实际做的事只有三件写采集服务、配告警规则、做展示看板。这才是医院场景真正需要定制的地方其他部分直接用社区项目就好不要重复造轮子。6.2 扩展方向与开源边界后续扩展我考虑了三个方向接入更多设备类型把精密空调、漏水检测、配电柜状态一起纳入构成完整的动力环境监控平台。引入电池寿命预测用历史放电数据做简单的容量衰减曲线拟合这个需要积累几个月的数据才有实际意义。与医院信息系统打通断电事件自动生成工单和资产管理系统联动让换电池、巡检维修的流程自动化。开源许可证我选的是MIT医院场景不存在版权顾虑系统集成商拿去做商业交付也没有障碍。这里我要多说一句边界问题这套系统在医疗机构里的定位是“运维工具”不是“医疗设备本身”。它做的是监测、预警、辅助决策绝不要试图让它去替代任何医疗设备的安全机制。把这个边界想清楚很多设计上的纠结就自然解开了。这套系统上线大半年我的体会是比UPS本身更重要的是让团队随时知道UPS的真实状态。它不会让你避免停电但能让你在停电的每一个阶段都掌握主动权。以后你值班时手机里收到的不该是黑暗中打来的求助电话而应该是一条提前到达的预警信息。希望这套开源方案也能帮你做到这件事。