拓冰建站拓冰建站
首页 / 资讯中心 / 正文

用Node.js和浏览器监控BeagleBone CPU温度:从原理到部署

简介面向 BeagleBone 物联网开发者的 Node.js 监控项目通过 WebSocket 以实时方式将 CPU 温度数据推送到浏览器页面。项目完全基于 JavaScript 编写涵盖服务端数据读取、实时通信和前端图表展示适合学习 Node.js 实时应用、传感器数据接入与嵌入式 Web 开发的初学者及进阶者也适合作为物联网课程的教学案例。压缩包约 21.43MB项目以 Node.js 源码工程为主通常含有服务端入口、依赖配置、静态页面与前端交互脚本等模块目录结构清晰便于快速定位与修改可快速部署到 BeagleBone 并在此基础上改造扩展。目前已有 65 人学习资源规模不大但链路完整从温度采集、Node.js 处理、WebSocket 推送到浏览器图表更新均有涉及有助于使学习者能直观理解物联网远程监控的典型实现。基于此项目开发者还可以加入温度阈值告警、历史数据存储或多设备管理深化对 Express、ws 库以及前端图表库的综合运用从而提升综合能力。 如果你手上有一块 BeagleBone又习惯像看云服务器监控一样盯 CPU 温度那这个叫 AdminApp 的小项目应该会很对胃口。它做的事情一句话就能讲清楚在 BeagleBone 上跑一个 Node.js Web 服务浏览器打开页面就能看到当前 CPU 温度还能顺带画一条历史曲线。适合谁用平时拿 BeagleBone 当小服务器、跑采集程序、或者长时间挂机跑任务的玩家不想为了看个温度每次都 SSH 进去敲命令的人都可以参考这套做法。我做这个项目的最初动机其实是去年夏天板子放在弱电箱旁边有一回半夜程序跑挂了排查了半天发现是热死的。从那以后我就决定给它装一个“体温计”而且这个体温计得做到打开手机就能看。这次我把完整思路、代码、部署步骤和踩过的坑都整理出来希望能帮同样用 BeagleBone 做长期任务的你少走弯路。1. 为什么要把 BeagleBone 的 CPU 温度开到浏览器里1.1 从一次“板子莫名死机”说起我有块 BeagleBone Black常年放在弱电箱旁边跑一个数据采集脚本24 小时不停。夏天最热那几天我连续两次在凌晨收到程序停止响应的消息SSH 进去翻内核日志发现是高温环境导致系统不稳定进程直接挂掉。查来查去根子就是板子过热。BeagleBone Black 用的是 TI AM3358 这颗 1GHz 的 ARM Cortex-A8 处理器本身功耗不算高但散热条件差、环境温度高的时候CPU 温度轻轻松松就能冲到 80°C 甚至 90°C。真正让我意外的是温度监控这件事居然被我拖了那么久才做——以前每次都是临时 SSH 进去敲一句cat /sys/class/thermal/thermal_zone0/temp看完数字就关掉完全没意识到应该做一个长期可回看的面板。那次故障之后我给自己列了三个需求第一温度必须实时可见第二必须在浏览器里看因为手机和电脑都能打开第三历史数据要能保留一段时间方便回看温度变化趋势。于是就有了这个叫 AdminApp 的小项目名字取得比较随意目标就是给 BeagleBone 做一个“管理员的查看面板”。1.2 AdminApp 的定位和使用场景这个项目的本质不是炫技而是解决一个很常见的嵌入式运维问题如何轻量地查看设备运行状态。技术栈非常简单BeagleBone Black 上安装 Node.jsNode.js 读取 Linux 内核暴露的温度文件再提供一个 HTTP 接口和一个 HTML 页面。浏览器访问http://板子IP:3000就能看到温度。整条链路没有数据库没有任何中间件所有数据都从 sysfs 直接读。这是我有意为之的监控面板这种工具链路越短越不容易坏。我见过有人为了监控一个温度在 512MB 内存的板子上引入 InfluxDB 加 Grafana还硬要塞一个 Docker 进去完全是用牛刀杀鸡。BeagleBone 的资源不是这么糟蹋的。如果你也想照着做适用的场景包括BeagleBone 或其他 ARM 板子跑长时间任务时的温度监控、设备放在不好操作的地方希望远程查看状态、以及你想给手头的开发板写一个“第一个正经 Node.js 项目”练手。注意如果只是想在电脑上看自己 PC 的温度那就不要拿 BeagleBone 折腾了PC 的读取路径跟 sysfs 不是一回事。这个项目用下来我最大的感受是不管人在工位还是躺床上只要浏览器能打开这个页面板子现在的状态一眼就知道。温度就是硬件的“血压”超了直接变红非常直观。2. 温度数据是怎么从 CPU 到 Linux 的2.1 先用手摸清 BeagleBone 的温度传感器接口做这个项目之前我一直以为读 CPU 温度需要调用什么底层驱动真正做出来才发现真相简单到令人发指根本不需要任何第三方库。BeagleBone Black 使用的 AM335x 芯片内部集成了一颗温度传感器Linux 内核启动后会把它注册到 thermal zone然后通过/sys/class/thermal/thermal_zone0/这个目录暴露出来。我习惯在写代码之前先到命令行确认一下这个节点是否真实存在debianbeaglebone:~$ ls /sys/class/thermal/ thermal_zone0 debianbeaglebone:~$ cat /sys/class/thermal/thermal_zone0/type cpu-thermal第一行确认存在 thermal_zone0第二行查看类型通常能看到cpu-thermal。这一步很重要它能帮你确认路径跟我接下来代码里写的是否一致因为不同系统镜像上的路径可能有差异。BeagleBone 的官方 Debian 镜像一般就是 thermal_zone0但也有个别版本会把温度传感器放在 hwmon 下面后面我会讲怎么处理。2.2 毫摄氏度单位换算别把 5 万当 5 度再看温度值debianbeaglebone:~$ cat /sys/class/thermal/thermal_zone0/temp 52841第一次看到这个数字时我差点以为 CPU 已经烧到 5 万多度了。其实这里的单位是毫摄氏度也就是千分之一摄氏度52841 毫摄氏度等于 52.841 摄氏度。也就是说代码里拿到这个整数之后必须先除以 1000再保留两位小数输出给前端。这个单位换算是整个项目里最容易出 bug 的地方之一。少除一次前端就会显示一个几十万的温度第一次看到的人绝对会怀疑板子要炸。所以我在代码里专门写了一个小函数让所有读数都在这一个地方完成换算其他地方谁也不要再去碰原始整数function readCPUTemp() { const raw fs.readFileSync(/sys/class/thermal/thermal_zone0/temp, utf8).trim(); return (parseInt(raw, 10) / 1000).toFixed(2); }2.3 找不到 thermal_zone0 时的备用方法不是所有 BeagleBone 镜像都走得那么顺。我有一次刷了精简版系统/sys/class/thermal/下面居然空空的当时以为内核驱动没加载。排查后发现温度传感器的另一个常见出口是 hwmon 子系统。命令行手动探测的方法是这样的debianbeaglebone:~$ ls /sys/class/hwmon/ hwmon0 debianbeaglebone:~$ cat /sys/class/hwmon/hwmon0/temp1_input 52841hwmon 节点的命名规律是 temp1_input、temp2_input不一定哪个对应 CPU。BeagleBone 上温度传感器一般只有一个所以看到某个数字跟当前发热状态对得上比如跑一个负载后数字蹭蹭涨基本就是它了。如果你想更稳妥可以在 Node.js 里写一个自动探测函数运行时依次尝试多个候选路径取第一个能读到的。这个兜底逻辑不过几行代码但能帮你省去以后换镜像再改代码的麻烦。我建议把这个函数封装成一个独立模块主文件会干净很多。3. Node.js 后端一个文件搞定数据接口3.1 我不装框架用原生 http 模块的原因这个项目里我用了 Node.js 的原生 http 模块没有引入 Express。说实话Express 本身不是坏选择很多项目我都会用。但在这个场景里我只有一个接口读温度和一个静态页面为这么点事往 node_modules 里塞一堆依赖系统备份和迁移反而麻烦。原生 http 模块写起来不复杂十几行代码就能撑起这个服务。Node.js 的事件循环天生适合“每次收到请求就读一个小文件然后回 JSON”这种轻 IO 场景。如果你更习惯 Express完全可以照用把路由换成app.get(/api/temperature, ...)就行原理一模一样。这个选择没有绝对的对错只是个偏好问题。3.2 核心代码拆解读取温度与 JSON 输出下面是后端 server.js 的核心结构注释写得很详细方便直接照着改const http require(http); const fs require(fs); const path require(path); function readCPUTemp() { const candidates [ /sys/class/thermal/thermal_zone0/temp, /sys/class/hwmon/hwmon0/temp1_input ]; for (const file of candidates) { try { const raw fs.readFileSync(file, utf8).trim(); return { temp: (parseInt(raw, 10) / 1000).toFixed(2), source: file }; } catch (e) { // 继续尝试下一个路径 } } return { temp: null, source: none }; } const server http.createServer((req, res) { if (req.url /api/temperature) { const data readCPUTemp(); res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify({ ...data, timestamp: Date.now() })); return; } // 静态文件public 目录下 let filePath path.join(__dirname, public, req.url / ? index.html : req.url); // 常规静态文件读取和返回逻辑 }); server.listen(3000, 0.0.0.0, () { console.log(AdminApp listening on http://0.0.0.0:3000); });这里有一个细节必须提醒你listen的第二个参数我写的是0.0.0.0不是默认省略。如果你只写server.listen(3000)大多数情况下确实也能从局域网访问但有些 Node.js 版本的行为会让人踩坑可能只绑定到 IPv6 的::或只监听本机回环地址导致你从电脑上用板子 IP 访问时直接超时。显式绑定到 0.0.0.0就是告诉系统“允许来自任何网络接口的访问请求”。这个坑在 Express 里也存在所以别嫌多写一个参数麻烦。3.3 要不要上 WebSocket轮询足够的原因后端写完下一个问题就是温度数据怎么推给前端常见两个方案WebSocket 实时推送或者前端每隔几秒发起一次轮询。我选了轮询理由很朴素温度变化频率没那么高2 秒拉一次已经非常平滑了。BeagleBone 上最多同时开几个人看面板每 2 秒一个轻量 HTTP 请求对这台板子来说几乎可以忽略不计。WebSocket 当然也可以长连接保持、数据即时推送听起来更“高级”但它引入了连接管理的复杂度和断线重连逻辑收益只是把延迟从 1 秒级降到毫秒级。对 CPU 温度这种慢变量这个收益没有实际意义。简洁性在嵌入式环境里就是稳定性一个纯 HTTP 轮询的服务几乎不可能因为连接管理问题出故障而带 WebSocket 的服务如果代码写得不仔细反而容易冒出各种奇怪异常。4. 前端页面给温度加一个能看的壳4.1 最简版 HTMLfetch 轮询页面前端我放在 public 静态目录里服务端把 index.html 返回给浏览器。页面核心逻辑分三块一个大号温度数字、一个颜色状态提示、一个历史曲线。先说最基础的温度数字怎么实时刷新。我使用 setInterval 定时调用 fetch 访问/api/temperature拿到 JSON 后更新页面 DOMdiv idtemp--/div script async function refresh() { try { const resp await fetch(/api/temperature); const data await resp.json(); const tempEl document.getElementById(temp); tempEl.textContent data.temp °C; if (parseFloat(data.temp) 70) tempEl.style.color red; else tempEl.style.color #333; } catch (e) { document.getElementById(temp).textContent 连接失败; } } setInterval(refresh, 2000); refresh(); /script哪怕页面做到这里已经能用了。“温度超过 70 变红”是我强烈建议加的一点人对颜色的敏感程度远高于数字你不可能每分钟都盯着数字看但瞥一眼页面有没有变红就够了。70 这个阈值根据你的实际散热条件调整散热好的板子常年 50 多度阈值设到 75、80 都行像我这种放弱电箱里的60 就得开始警惕。4.2 用 Chart.js 画一条实时温度曲线光看当前温度只能知道“现在怎么样”想知道“过去一小时有没有异常波动”必须上曲线。我给页面加了历史曲线用的是 Chart.js 这个经典前端图表库。这里要提醒你别指望开发板能顺畅访问公网 CDN直接加载https://cdn.jsdelivr.net/npm/chart.js很慢甚至失败。我习惯先把 chart.umd.js 下载到 BeagleBone 的 public 目录里做成一个完全离线的页面。用法很简单前端维护一个数组每次 fetch 到新温度就往数组里推然后更新 Chart.js 的 dataconst chart new Chart(ctx, { type: line, data: { labels: [], datasets: [{ data: [], borderColor: #e8982a }] }, options: { animation: false } }); function pushData(label, value) { chart.data.labels.push(label); chart.data.datasets[0].data.push(value); chart.update(); }为了不让曲线无限增长我把它限制在最近 120 个点约 4 分钟的数据。每隔一段时间就把数组头部的旧数据 shift 掉。如果你想要更长时间的历史更省内存的做法是后端在内存里维护一个环形缓冲区前端首次加载时把历史数据全部返回后续再用轮询做增量更新。4.3 静态资源与 API 由同一个 Node 服务托管你已经看到了页面静态资源和 API 接口跑在同一个端口上。这样做的好处是省事、没有跨域问题浏览器访问页面本身页面里的 fetch 请求同样指向/api/temperature天然同源根本不用碰 CORS 那套东西。之前有人说“前端要么单独部署、要么做前后端分离”我觉得在小工具项目里这是过度工程。AdminApp 的核心诉求是让人打开页面就看到温度一个 Node 进程把页面和接口都承包了重启一次服务全部恢复。这种单体小应用模式在开发板上反而是最优解不需要 Nginx不需要协调静态文件服务器和 Node 进程之间的端口。我自己甚至建议把 HTML、CSS、JS 尽量压缩到尽可能少的文件里最好只留一个 index.html 加一个图表库文件以后整个 public 目录拷到任何设备上都能直接用。5. 部署到板子上安装 Node.js 与开机自启5.1 别再纠结 apt 的旧版 Node直接装 armv7l 包BeagleBone Black 的系统一般是 Debian 系执行sudo apt install nodejs之后可能会发现装了一个非常古老的 Node.js 版本。跑这种小项目老版本也不是完全不行但语法支持、npm 依赖方面都会受限而且老版本有安全风险。我的建议是直接去 nodejs.org 下载官方预编译的 ARM 版本。先确认架构。BeagleBone Black 是 32 位 ARM 处理器uname -m一般输出armv7l那就下载node-v18.x.x-linux-armv7l.tar.xz。解压后放到/usr/local/再把 node 与 npm 软链接到/usr/local/bin/下三步搞定比 apt 源里的版本可控得多。如果你用的是 BeagleBone AI 或 BeagleBone AI-64处理器是 64 位 ARM要下载linux-arm64的包。判断方法非常简单uname -m输出 aarch64 就用 arm64输出 armv7l 就用 armv7l别自己猜。5.2 用 systemd 把它变成常驻服务部署最后一步是让 AdminApp 开机自启并且进程意外退出能自动拉起。我一开始图省事用 nohup 放后台跑结果板子一重启服务就没了必须手动 SSH 登录再启动。后来改成 systemd 服务一劳永逸。在/etc/systemd/system/adminapp.service写一个基本 unit 文件[Unit] DescriptionAdminApp BeagleBone Temperature Monitor Afternetwork.target [Service] ExecStart/usr/local/bin/node /home/debian/adminapp/server.js Restartalways RestartSec3 Userdebian [Install] WantedBymulti-user.target最关键的是Restartalways它保证进程一退出 systemd 就自动拉起RestartSec3是重启前等待 3 秒防止进程出问题时高频重启刷爆日志。注意ExecStart里的 node 必须写绝对路径which node能查到。写完执行sudo systemctl daemon-reload sudo systemctl enable --now adminapp之后每次开机服务都会自动运行。访问时注意如果 BeagleBone 正通过 USB 线插在电脑上板子虚拟出来的网卡 IP 一般是 192.168.7.2浏览器打开http://192.168.7.2:3000如果接了网线或 WiFi就用对应的局域网 IP 访问。我之前一直用 USB 连接调试后来把板子挪到弱电箱走网线访问地址变了这个细节差点让我误以为服务挂了。5.3 实测中踩过的四个坑最后汇总四个我在实际部署中排查过的坑照着做能帮你省下不少时间症状根因解决办法局域网内其他设备打不开页面服务只绑定了 127.0.0.1 或默认 IPv6listen里显式写0.0.0.0页面打开但温度显示几万度没做毫摄氏度换算读取整数后除以 1000找不到温度文件程序报错镜像没有 thermal_zone0 节点增加 hwmon 路径兜底循环尝试图表的 JS 加载失败页面依赖了公网 CDN把图表库下载到 public 目录本地引用这四个坑的类型各不相同第一个是网络绑定第二个是常见的单位陷阱第三个是不同系统镜像差异第四个是前端资源依赖问题但它们都有一个共同特点——都在“看似正常”的表象下悄悄影响结果。比如绑定地址那个坑在板子上 curl 明明能通浏览器就是打不开不抓包排查真的很容易一头雾水。最后再分享一个我后来加的小功能给面板加了一个设备身份提示。因为我手上不止一块 BeagleBone不同板子 IP 不一样有时打开面板会分不清自己在看哪台。我在/api/temperature的返回值里带上os.hostname()和基础网络信息前端显示在页面左上角这样打开任何一个面板都能确认当前是哪台设备。AdminApp 做到这个程度对我来说已经完全够用了希望这套方案也能帮你省下部分“难怪死机了”的排查时间。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门