好想给自己的网页加点功能
环境准备
考虑到树莓派对 python 的支持良好,且 python 的各种包实在是非常好用,这里用 python 的 Flask 框架作为网页后端
树莓派 raspberryPi OS 中的 python 环境作为系统服务,是不允许直接使用 pip 进行包管理的,这里我们使用 Debian 维护的,非常好用的 apt 包管理器来安装需要的 python 包
有良好习惯的同学们可能有疑问,为什么不使用 python 进行虚拟环境的隔离呢?因为虚拟环境中无法直接调用树莓派有关系统环境的 API,需要使用python -m venv --system-site-packages <虚拟环境路径>
参数在虚拟环境中调用全局系统包,那我为什么不直接用全局环境呢?我暂时没有发现比较好的办法
我们使用以下命令下载需要的包
1 2 3 4 5
| # 更新系统包列表 sudo apt update
# 安装所需的 Python 包 sudo apt install python3-flask python3-psutil python3-flask-cors
|
这里,pyhton3-flask
和python-flask-cors
分别是 Flask 框架和对应的跨域服务,python3-psutil
则是调用系统状态的 API 所必要的包
服务器实现
在你喜欢的地方,写一个后端提供系统数据的 API,这里我在 /home/kpmark/nav/status.py
中进行编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import psutil from flask import Flask, jsonify from flask_cors import CORS
app = Flask(name)
CORS(app, resources={ r"/status": { "origins": ["https://www-blog.u2330056.nyat.app:49191"], "methods": ["GET"], "allow_headers": ["Content-Type"] } })
@app.route('/status') def get_status(): cpu_percent = psutil.cpu_percent(interval=1) memory = psutil.virtual_memory() disk = psutil.disk_usage('/') temperatures = psutil.sensors_temperatures()
cpu_temp = None if 'cpu_thermal' in temperatures: cpu_temp = temperatures['cpu_thermal'][0].current
return jsonify({ 'cpu_percent': cpu_percent, 'memory_percent': memory.percent, 'disk_percent': disk.percent, 'cpu_temp': cpu_temp, 'memory_total': memory.total, 'memory_used': memory.used, 'disk_total': disk.total, 'disk_used': disk.used })
if name == 'main': app.run(host='127.0.0.1', port=3001)
|
这样,就有一个后端可以挂在你树莓派的 3001 端口了,但是我们想让他自动在后台启动,需要配置相应的系统服务
配置系统服务
这里,使用以下命令创建 systemd 服务文件
1 2
| sudo cd /etc/systemd/system/ sudo touch raspstatus.service
|
然后填入以下内容
1 2 3 4 5 6 7 8 9 10 11 12
| [Unit] Description=Raspberry Pi Status API After=network.target
[Service] User=kpmark WorkingDirectory=/home/kpmark/nav ExecStart=/usr/bin/python3 /home/kpmark/nav/status.py Restart=always
[Install] WantedBy=multi-user.target
|
- Description:描述服务的名称和用途,这里是 “Raspberry Pi Status API”
- After:指定服务启动的依赖顺序,表示该服务将在
network.target
(网络服务)启动后才启动,确保网络可用
- User:指定运行该服务的用户,这里是
kpmark
。服务会以该用户的权限运行
- WorkingDirectory:设置服务的工作目录,这里是
/home/kpmark/nav
。服务启动时会切换到该目录
- ExecStart:指定启动服务的命令,这里是使用 Python 3 执行
/home/kpmark/nav/status.py
脚本
- Restart:定义服务在什么情况下重启。
always
表示无论服务因何原因退出,都会自动重启,确保服务持续运行
- WantedBy:指定服务的启动级别或目标。
multi-user.target
是多用户环境的目标,表示该服务会在系统启动时自动启动
然后,我们通过以下命令启动该服务
1 2 3
| sudo systemctl daemon-reload sudo systemctl enable raspstatus sudo systemctl start raspstatus
|
出现问题的话,可以通过以下命令调出服务日志进行查看
1 2
| sudo journalctl -u raspstatus -f
|
Nginx 配置
由于涉及到后端的跨域请求,这里需要对 Nginx 进行一些配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| # 状态API代理 location /status { proxy_pass http://127.0.0.1:3001/status; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # CORS 配置 add_header 'Access-Control-Allow-Origin' 'https://www-blog.u2330056.nyat.app:49191'; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' 'https://www-blog.u2330056.nyat.app:49191'; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; }
|
这里我们直接将 /status
转发至后端,这样就不用在前端写出完整的请求路由了
- proxy_pass:将匹配的请求代理到
http://127.0.0.1:3001/status
,即转发到本地的 3001 端口上
- proxy_set_header:设置代理请求的
Host
头为原始请求的 Host
值,确保后端服务能正确识别请求的域名
- proxy_set_header:设置代理请求的
X-Real-IP
头为客户端 IP 地址,方便后端服务获取真实 IP
- add_header:设置响应头
Access-Control-Allow-Origin
,允许来自 https://www-blog.u2330056.nyat.app:49191
的跨域请求
- add_header:设置响应头
Access-Control-Allow-Methods
,允许的 HTTP 方法为 GET
和 OPTIONS
- add_header:设置响应头
Access-Control-Allow-Headers
,允许的请求头包括 DNT
、User-Agent
等
- add_header:设置响应头
Access-Control-Expose-Headers
,允许客户端访问 Content-Length
和 Content-Range
头信息
还有一堆 OPTIONS
请求处理,这里不再赘述其含义
前端实现
这里给出我的前端 demo,仅供参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| <div class="card status-card"> <h2>树莓派系统状态</h2> <p>实时系统资源监控</p> <div class="status-grid"> <div class="status-item"> <h3>CPU 使用率</h3> <p id="cpu-percent">--</p> <div class="status-bar"> <div class="status-bar-fill" id="cpu-bar" style="width: 0%"></div> </div> </div> <div class="status-item"> <h3>内存使用率</h3> <p id="memory-percent">--</p> <div class="status-bar"> <div class="status-bar-fill" id="memory-bar" style="width: 0%"></div> </div> </div> <div class="status-item"> <h3>CPU 温度</h3> <p id="cpu-temp">--</p> <div class="status-bar"> <div class="status-bar-fill" id="temp-bar" style="width: 0%"></div> </div> </div> <div class="status-item"> <h3>磁盘使用率</h3> <p id="disk-percent">--</p> <div class="status-bar"> <div class="status-bar-fill" id="disk-bar" style="width: 0%"></div> </div> </div> </div> </div>
<script> function formatPercent(value) { return `${value.toFixed(1)}%`; }
function formatTemp(value) { return `${value.toFixed(1)}°C`; }
function updateStatus() { fetch('/status') .then(response => response.json()) .then(data => { document.getElementById('cpu-percent').textContent = formatPercent(data.cpu_percent); document.getElementById('cpu-bar').style.width = `${data.cpu_percent}%`; document.getElementById('memory-percent').textContent = formatPercent(data.memory_percent); document.getElementById('memory-bar').style.width = `${data.memory_percent}%`; if (data.cpu_temp !== null) { document.getElementById('cpu-temp').textContent = formatTemp(data.cpu_temp); document.getElementById('temp-bar').style.width = `${(data.cpu_temp / 80) * 100}%`; } document.getElementById('disk-percent').textContent = formatPercent(data.disk_percent); document.getElementById('disk-bar').style.width = `${data.disk_percent}%`; }) .catch(error => console.error('Error fetching status:', error)); }
updateStatus(); setInterval(updateStatus, 2000); </script>
|
这里我使用原生页面简单的实现了卡片效果如下,也可以使用现代前端框架实现更加美观的界面
