Skip to content

Commit f6cae6a

Browse files
committed
new(ups): new posts
1 parent fdd4c6b commit f6cae6a

1 file changed

Lines changed: 299 additions & 0 deletions

File tree

content/posts/ups/index.md

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
+++
2+
date = '2026-04-22T23:00:09+08:00'
3+
draft = false
4+
title = 'UPS 折腾记'
5+
+++
6+
7+
## 前言
8+
9+
最近我突然有了两台 ups 的管理权,一台工作室的 APC Smart-UPS SPRM1K,另一台家里的 APC BACK-UPS BK650。由于手边只有前者,先更新前者的研究成果,后者择日更新。
10+
11+
管理带通信的 UPS 的配套软件其实就两种,这里主要讲解 NUT,而 apcupsd 配置很简单,而且也可以作为 NUT 的驱动,这里不再详述。
12+
13+
## NUT 的配置
14+
15+
要理解 NUT,首先要明白几个概念:driver、monitor(client)、server(可选),三个服务都要单独启用。
16+
17+
### 驱动层
18+
19+
driver 其实就是对接各种奇奇怪怪 ups 的,这里我这两台一个用的是串口(apcsmart),另一个是 usbhid-ups,这里主要是以前者讲解的。
20+
21+
配置 ups 驱动:
22+
```bash
23+
## /etc/nut/ups.conf
24+
## 根据需要填写,可用nut-scanner -U获取驱动
25+
[ups]
26+
driver = apcsmart
27+
port = /dev/ups
28+
cable = 940-0024
29+
ignorelb # 手动指定阈值
30+
override.battery.charge.low = 70
31+
override.battery.runtime.low = 20
32+
```
33+
34+
启动服务:`upsdrvctl start`
35+
36+
这里有个小插曲,串口可能提示权限不够:
37+
```bash
38+
root@pve:/etc/nut# upsdrvctl start
39+
Network UPS Tools - UPS driver controller 2.8.1
40+
Network UPS Tools - Generic HID driver 0.52 (2.8.1)
41+
USB communication driver (libusb 1.0) 0.46
42+
libusb1: Could not open any HID devices: insufficient permissions on everything
43+
No matching HID UPS found
44+
upsnotify: notify about state 4 with libsystemd: was requested, but not running as a service unit now, will not spam more about it
45+
upsnotify: failed to notify about state 4: no notification tech defined, will not spam more about it
46+
Driver failed to start (exit status=1)
47+
```
48+
49+
用 udev 规则,给 nut 权限:
50+
```bash
51+
cat << 'EOF' > /etc/udev/rules.d/99-nut-ups.rules
52+
SUBSYSTEM=="usb", ATTR{idVendor}=="051d", ATTR{idProduct}=="0002", MODE="0660", GROUP="nut"
53+
EOF
54+
udevadm control --reload-rules
55+
udevadm trigger
56+
```
57+
58+
再次启动:`upsdrvctl start`
59+
60+
### 服务层
61+
62+
nut 其实 c/s 架构很分明,如果只有一台机子,也得启动一个服务器。但是这也为拓展型带来了便利。
63+
64+
如果你只有一台机子,用 standalone 即可,作为“吹哨人”,用 netserver,接收命令关闭自己的机器,用 netclient。
65+
66+
我这里改成服务器:
67+
```bash
68+
## /etc/nut/nut.conf
69+
# 服务器模式
70+
MODE=netserver
71+
72+
## /etc/nut/upsd.conf
73+
# 加上这一行,给局域网里面的设备提供服务
74+
# 如果你是pve而且路由器不接ups,建议自己建一个
75+
# 自定义交换机
76+
LISTEN 0.0.0.0 3493
77+
```
78+
79+
配置用户,monuser 最好有,因为群晖用这个而且不能更改。
80+
```bash
81+
## /etc/nut/upsd.users
82+
[monuser]
83+
password = secret
84+
upsmon slave
85+
[admin]
86+
password = <your_password>
87+
upsmon master
88+
actions = SET
89+
instcmds = ALL
90+
```
91+
92+
启动服务:`systemctl enable --now nut-server`
93+
94+
### 客户端/监视层
95+
96+
配置 nut-monitor 以便自动关机:
97+
```bash
98+
MONITOR ups@localhost 1 monuser secret master # slave也可以,后文(关机过程)讲
99+
```
100+
101+
你可以看到,最下面有个`shutdown`命令,这个只要 monitor 服务启动了,在遇到低电量阈值的时候,就一定会执行的。
102+
103+
启动服务:`systemctl enable --now nut-monitor`
104+
105+
106+
## 关机过程
107+
108+
1. 断开电源,nut 检测到电池供电,其他客户端收到信号。
109+
110+
对于群晖这种设置了 upssched 的机子,他可以在检测到断电之后的一定时间开启安全模式(umount 所有分区,只保留必须的 nut 监听服务和关机逻辑),如果在此期间市电恢复,重新检测到后会进行自动重启。
111+
112+
2. 电池到达所给定的阈值(电量/剩余事件/电池供电时间),发出警告信号。
113+
114+
这时候服务器自己会开始关机(执行 shutdown),其他客户端会收到信号,开始进行关机。
115+
116+
在关机最后一刻,有一个有意思的脚本想和大家分享。
117+
118+
这个脚本是 systemd 在关机的最后时刻执行的,位于`/usr/lib/systemd/system-shutdown/nutshutdown`。我们来看看他的结构:
119+
120+
```bash
121+
#!/bin/sh
122+
123+
# This script requires both nut-server (drivers)
124+
# and nut-client (upsmon) to be present locally
125+
# and on mounted filesystems
126+
[ -x "/sbin/upsmon" ] && [ -x "/sbin/upsdrvctl" ] || exit
127+
128+
if /sbin/upsmon -K >/dev/null 2>&1; then
129+
# The argument may be anything compatible with sleep
130+
# (not necessarily a non-negative integer)
131+
wait_delay="`/bin/sed -ne 's#^ *POWEROFF_WAIT= *\(.*\)$#\1#p' /etc/nut/nut.conf`" || wait_delay=""
132+
133+
/sbin/upsdrvctl shutdown
134+
135+
if [ -n "$wait_delay" ] ; then
136+
/bin/sleep $wait_delay
137+
# We need to pass --force twice here to bypass systemd and execute the
138+
# reboot directly ourself.
139+
/bin/systemctl reboot --force --force
140+
fi
141+
fi
142+
143+
exit 0
144+
```
145+
146+
可以注意到经过一系列的判断,如果是真的断电(发出警告后)`upsmon -K`会返回 1,那么就会使用`upsdrvctl shutdown`来命令 ups 在一定时间后断电。
147+
148+
但是这时候,系统已经解除了所有的磁盘 rw 的挂载,执行完脚本的下一步其实就是给 acpi 电源发送 S5 信号并断电,速度很快,其实断不断电也无所谓了。
149+
150+
一般的断电延时 20s 绰绰有余。至于断电时间的设置,高级的 ups 可以设置(见后文`upsrw`),普通的不可以。
151+
152+
所以如果这是最后一个“吹哨人”执行的 killpower(让 ups 等会断电),最好关闭的时候要尽量比 slave 关得晚,否则就得尽量延长 ups 的延迟时间。
153+
154+
## 常用命令
155+
156+
### upscmd:执行一些驱动预设的指令
157+
158+
```bash
159+
root@SAST-Docker:~# upscmd -l ups
160+
Instant commands supported on UPS [ups]:
161+
162+
bypass.start - Put the UPS in bypass mode
163+
bypass.stop - Take the UPS out of bypass mode
164+
load.off - Turn off the load immediately
165+
load.on - Turn on the load immediately
166+
shutdown.return - Turn off the load and return when power is back
167+
shutdown.stayoff - Turn off the load and remain off
168+
test.battery.start - Start a battery test
169+
test.battery.stop - Stop the battery test
170+
test.failure.start - Start a simulated power failure
171+
test.panel.start - Start testing the UPS pane
172+
```
173+
174+
### upsrw:用于读写 eeprom 的
175+
176+
使用:
177+
```bash
178+
upsrw -l <upsname>
179+
```
180+
181+
控制响声、复电延迟启动、复电要求电池水平、关电延迟停止 ups。
182+
183+
比如我们工作室的 SPRM1K 可以设置的东西就很多,还贴心的用了 enum 告诉你:
184+
```bash
185+
root@SAST-Docker:/etc/nut# upsrw -l ups
186+
[battery.alarm.threshold]
187+
Battery alarm threshold
188+
Type: ENUM NUMBER
189+
Option: "0" SELECTED
190+
Option: "T"
191+
Option: "L"
192+
Option: "N"
193+
194+
[battery.charge.restart]
195+
Minimum battery level for restart after power off (percent)
196+
Type: ENUM NUMBER
197+
Option: "00"
198+
Option: "15"
199+
Option: "50" SELECTED
200+
Option: "90"
201+
202+
[battery.date]
203+
Battery change date
204+
Type: STRING
205+
Maximum length: 8
206+
Value: 12/30/25
207+
208+
[input.transfer.high]
209+
High voltage transfer point (V)
210+
Type: ENUM NUMBER
211+
Option: "231"
212+
Option: "242"
213+
Option: "253"
214+
Option: "264" SELECTED
215+
216+
[input.transfer.low]
217+
Low voltage transfer point (V)
218+
Type: ENUM NUMBER
219+
Option: "187"
220+
Option: "176" SELECTED
221+
Option: "165"
222+
Option: "154"
223+
224+
[output.voltage.nominal]
225+
Nominal output voltage (V)
226+
Type: ENUM NUMBER
227+
Option: "220" SELECTED
228+
Option: "230"
229+
Option: "240"
230+
231+
[ups.delay.shutdown]
232+
Interval to wait after shutdown with delay command (seconds)
233+
Type: ENUM NUMBER
234+
Option: "020"
235+
Option: "180"
236+
Option: "300" SELECTED
237+
Option: "600"
238+
239+
[ups.delay.start]
240+
Interval to wait before (re)starting the load (seconds)
241+
Type: ENUM NUMBER
242+
Option: "000"
243+
Option: "060" SELECTED
244+
Option: "180"
245+
Option: "300"
246+
247+
[ups.id]
248+
UPS system identifier
249+
Type: STRING
250+
Maximum length: 8
251+
Value: UPS_IDEN
252+
253+
[ups.test.interval]
254+
Interval between self tests (seconds)
255+
Type: ENUM NUMBER
256+
Option: "1209600" SELECTED
257+
Option: "604800"
258+
Option: "0"
259+
```
260+
261+
我在这里改了`ups.delay.shutdown`,原因很尴尬,esxi 只支持客户端模式的 nut,服务端只能用一台虚拟机来实现。那么希望在服务端发出 killpower 后,还能等待 esxi 关闭,这个时间就不可避免的延长。
262+
263+
---
264+
265+
我还改了`ups.delay.start`,这个参数很有意思,他没有注释,根据字面意思理解,应该是断电之后延迟启动吧?
266+
267+
但是不是这个意思。他指的是如果你发出了 killpower 的命令后中途来电,他断电后,应该在多少秒后开机?
268+
269+
我觉得这句话得细品。
270+
271+
对于我这台 SPRM1K 来说,发送 killpower 指令后,首先是等待`ups.delay.shutdown`的时间,然后切断输出,然后过了硬编码的 1 分钟,关闭 ups 自己。
272+
273+
那么就有几种情况:
274+
275+
- 在等待`ups.delay.shutdown`时,市电恢复。
276+
- 在等待硬编码的 1 分钟的时候,市电恢复。
277+
- ups 关机之后,市电恢复。
278+
279+
这三种情况中前两者会等待`ups.delay.start`的时间,最后一种不会等待,初始化之后直接开机。
280+
281+
我想应该是[这篇文章](https://www.chiphell.com/forum.php?mod=viewthread&tid=2605539&extra=page%3D1&mobile=no)里面提到的问题,作者说如果断电的时间太短,那么 After AC Loss 就算设置为 Power On,也不一定能自动开机。所以才有这么一个选项。
282+
283+
> btw: 我并不太赞同链接里面的方案,复杂度太高了很难维护,其实里面很多东西厂家已经做好了。(排除 UPS 不够高端的问题,这个等我回去测试一下我的 BK650 再下定论)
284+
285+
## 奇怪的 bug
286+
287+
根据 `man 8 usbhid-ups` 中的说明,可以直接在 ups.conf 里面配置 `allow_killpower` 来在启动的时候生效,但是[这个 issue](https://github.com/networkupstools/nut/issues/2605)`allow_killpower` 在 2.8.3 之后才修复启动的时候硬编码的 0 会覆盖回去的问题,然而 trixie 是 2.8.1,就很难受。
288+
289+
能用点 hack 的办法,启动的时候修改。
290+
291+
```bash
292+
systemctl edit nut-driver@ups.service
293+
# 写入
294+
[Service]
295+
# 延迟几秒确保驱动已经完全初始化
296+
ExecStartPost=/bin/sh -c "sleep 5 && /usr/bin/upsrw -s driver.flag.allow_killpower=1 -u admin -p <你的密码> ups"
297+
```
298+
299+
但是这台设备不再身边,所以我还是选择了保守的 apcupsd 方案关机+nut 桥接给群晖,这篇文章之后还会更新的。

0 commit comments

Comments
 (0)