好想给自己的网页加点功能
环境准备
考虑到树莓派对 python 的支持良好,且 python 的各种包实在是非常好用,这里用 python 的 Flask 框架作为网页后端
树莓派 raspberryPi OS 中的 python 环境作为系统服务,是不允许直接使用 pip 进行包管理的,这里我们使用 Debian 维护的,非常好用的 apt 包管理器来安装需要的 python 包
有良好习惯的同学们可能有疑问,为什么不使用 python 进行虚拟环境的隔离呢?因为虚拟环境中无法直接调用树莓派有关系统环境的 API,需要使用python -m venv --system-site-packages <虚拟环境路径>参数在虚拟环境中调用全局系统包,那我为什么不直接用全局环境呢?我暂时没有发现比较好的办法
我们使用以下命令下载需要的包
# 更新系统包列表
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中进行编写
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温度(针对树莓派)
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 服务文件
sudo cd /etc/systemd/system/
sudo touch raspstatus.service
然后填入以下内容
[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是多用户环境的目标,表示该服务会在系统启动时自动启动
然后,我们通过以下命令启动该服务
sudo systemctl daemon-reload
sudo systemctl enable raspstatus
sudo systemctl start raspstatus
出现问题的话,可以通过以下命令调出服务日志进行查看
# 查看服务日志
sudo journalctl -u raspstatus -f
Nginx 配置
由于涉及到后端的跨域请求,这里需要对 Nginx 进行一些配置
# 状态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,仅供参考
<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() {
// 使用当前域名的status路径
fetch("/status")
.then((response) => response.json())
.then((data) => {
// Update CPU
document.getElementById("cpu-percent").textContent = formatPercent(
data.cpu_percent,
);
document.getElementById("cpu-bar").style.width = `${data.cpu_percent}%`;
// Update Memory
document.getElementById("memory-percent").textContent = formatPercent(
data.memory_percent,
);
document.getElementById("memory-bar").style.width =
`${data.memory_percent}%`;
// Update Temperature
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}%`;
}
// Update Disk
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));
}
// Update every 2 seconds
updateStatus();
setInterval(updateStatus, 2000);
</script>
这里我使用原生页面简单的实现了卡片效果如下,也可以使用现代前端框架实现更加美观的界面
