X-WRT 概览页显示 CPU 温度与 WiFi 温度
背景
X-WRT 26.04(Tenda BE12 Pro,mediatek/filogic,内核 6.18.44)的 LuCI 概览页默认不显示温度。底层数据其实一直存在:
- CPU 温度:
/sys/class/thermal/thermal_zone0/temp(毫摄氏度) - WiFi 温度:
/sys/class/ieee80211/phy0/hwmon*/temp1_input(毫摄氏度,2.4G / 5G 各一个)
目标:在概览页显示一行温度信息,不破坏原有页面结构,固件升级后重新放回文件即可恢复。
方案
利用 X-WRT 新版 LuCI 概览页的模块化加载机制:
- 主
index.ut(ucode 模板)渲染时会加载admin_status/luaindex子视图 luaindex.htm(Lua 视图)会扫描/usr/lib/lua/luci/view/admin_status/index/目录,把所有.htm片段include进来
我们在这个目录里新建一个片段 99-temperature.htm,它负责:
- 读 CPU 和 WiFi 温度
- 把值写进
window.__cpuTemp/window.__wifiTemp - 在
#view容器外面创建一个横条,显示这两个温度
关键设计:横条插在 #view 外面,因为 LuCI 概览页每 5 秒轮询重绘 #view 内容,插在里面会被冲掉。
完整文件
/usr/lib/lua/luci/view/admin_status/index/99-temperature.htm
<%
-- CPU 温度
local f = io.open("/sys/class/thermal/thermal_zone0/temp", "r")
local craw = f and f:read("*l") or nil
if f then f:close() end
local ctemp = craw and string.format("%.1f", tonumber(craw) / 1000) or "N/A"
-- WiFi 温度:扫所有 /sys/class/ieee80211/phy*/hwmon*/temp1_input
local wlist = {}
local p = io.popen("ls -d /sys/class/ieee80211/phy*/hwmon*/temp1_input 2>/dev/null")
if p then
for path in p:lines() do
local wf = io.open(path, "r")
local wraw = wf and wf:read("*l") or nil
if wf then wf:close() end
if wraw and tonumber(wraw) then
wlist[#wlist + 1] = string.format("%.1f", tonumber(wraw) / 1000)
end
end
p:close()
end
local wtemp = #wlist > 0 and table.concat(wlist, " / ") or "N/A"
%>
<script>
window.__cpuTemp = '<%=ctemp%> °C';
window.__wifiTemp = '<%=wtemp%> °C';
(function() {
if (!document.body || document.body.getAttribute('data-page') !== 'admin-status-overview')
return;
function build() {
var bar = document.getElementById('cpu-temp-bar');
if (!bar) {
var view = document.getElementById('view');
if (!view || !view.parentNode) return;
bar = document.createElement('div');
bar.id = 'cpu-temp-bar';
bar.style.cssText = 'margin:8px 0;padding:8px 12px;background:#fff3e0;border:1px solid #ffcc80;border-radius:6px;font-size:14px;';
view.parentNode.insertBefore(bar, view);
}
bar.textContent = 'CPU 温度: ' + window.__cpuTemp + ' WiFi 温度: ' + window.__wifiTemp;
}
setInterval(build, 500);
build();
})();
</script>
部署步骤
# 1. 写入片段文件
cat > /usr/lib/lua/luci/view/admin_status/index/99-temperature.htm <<'EOF'
(上面完整内容)
EOF
# 2. 清 LuCI 模板缓存
rm -rf /tmp/luci-*
# 3. 重启 web 服务
/etc/init.d/uwsgi restart 2>/dev/null
/etc/init.d/uhttpd restart 2>/dev/null
浏览器 Ctrl+F5 强刷。
效果
概览页“状态”标题下方、原来“系统”表格上方,出现一条橙色边框横条:
CPU 温度: 65.7 °C WiFi 温度: 49.0 / 53.0 °C
其中 49.0 / 53.0 是两个频段(2.4G / 5G)的温度。
温度值在页面加载时读一次。要看最新值,刷新页面即可。
原理
1. 为什么用 admin_status/index/ 目录
X-WRT 26.04 的 LuCI 概览页用了模块化视图。/usr/lib/lua/luci/view/admin_status/luaindex.htm 内容是:
local incdir = util.libpath() .. "/view/admin_status/index/"
if fs.access(incdir) then
for inc in fs.dir(incdir) do
if inc:match("%.htm$") then
includes[#includes + 1] = inc:gsub("%.htm$", "")
end
end
for _, inc in luci.util.vspairs(includes) do
include("admin_status/index/" .. inc)
end
end
只要目录存在,里面的 .htm 就会被自动加载。文件名的字母序决定加载顺序,99- 表示排在最后。
2. 为什么插在 #view 外面
概览页的 <div id="view"> 每 5 秒被 LuCI 轮询重绘。任何插在它内部的元素,下一轮就会被抹掉。插在它前面或后面(即 #view 的兄弟节点),就不会被影响。
3. 为什么用 setInterval 而不是一次性插入
横条第一次创建后,setInterval 每 500ms 检查一次 document.getElementById('cpu-temp-bar'),不存在就重建。这样即使页面发生某种重绘,横条也能自动恢复。
踩坑记录
| 尝试 | 结果 | 原因 |
|---|---|---|
用 sed 往 index.htm 插 <tr> |
失败 | index.htm 只是一个“加载器”,实际内容是异步渲染的 |
往 #view 里的表格插 <tr> |
显示后被冲掉 | 5 秒轮询会重绘整个 #view |
用 MutationObserver + 轮询移动 <tr> |
失败 | .includes 里的源元素被 LuCI 的 DOM 渲染逻辑清掉,getElementById 返回 null |
插在 #view 外面的横条 |
✅ 成功 | 不受 #view 重绘影响 |
排查过程中最有用的三条 Console 命令:
// 1. 看视图容器是否存在
document.getElementById('view') ? 'ok' : 'missing'
// 2. 看概览页表格结构
document.querySelectorAll('#view tr').length
// 3. 看温度行是否被保留
document.getElementById('cpu-temp-src') ? 'found' : 'not found'
hwmon 路径确认:
cat /sys/class/ieee80211/phy*/hwmon*/temp1_input
# 输出示例:
# 49000
# 53000
# 表示 49.0 °C / 53.0 °C,两个频段
备份与恢复
备份(修改前做):
cp /usr/lib/lua/luci/view/admin_status/index/99-temperature.htm /root/99-temperature.htm.bak 2>/dev/null
卸载(不想要了):
rm /usr/lib/lua/luci/view/admin_status/index/99-temperature.htm
rm -rf /tmp/luci-*
/etc/init.d/uwsgi restart 2>/dev/null
/etc/init.d/uhttpd restart 2>/dev/null
升级固件后:/usr/lib/lua/luci/view/admin_status/index/ 目录会被重置,把备份的 99-temperature.htm 放回去,清缓存重启即可。
可扩展方向
- 动态刷新:当前是页面加载时读一次。若需不刷新页面就自动更新,可以加一个小 cgi 接口(如
/www/cgi-bin/temp),前端用fetch每几秒请求一次。 - 区分频段:如果 hwmon 顺序固定(如
hwmon0对应 2.4G、hwmon1对应 5G),可以把49.0 / 53.0改成2.4G 49.0 / 5G 53.0。 - 显示风扇转速 / 电压:如果硬件支持,同样可以读
/sys/class/hwmon/下的对应文件。