TinyMonitor 是一个小巧极简的服务器状态监视终端,它仅由一个 ESP32 主控加上 OLED 显示屏,就可以将服务器的实时状态参数展示出来,方便观察调试。
前期准备
本项目用到的硬件物料非常简单,一个自带 Wi-Fi 蓝牙的 Beetle ESP32-C3,还有一块 128x64 的 OLED 屏。
Beetle ESP32-C3 的引脚定义如下。
因为可以使用软件 I2C 的方式(即自定义 I2C 引脚)驱动 OLED 屏,所以我将 Beetle ESP32-C3 的 0
/1
引脚定义为 SCL
/SDA
功能。这样一来,接线十分简单,相互贴着把 4 个引脚焊上就完成了。
注:给 Beetle ESP32-C3 烧录程序前,需要先添加 ESP32 的包,以正常识别板子型号。详见其 Wiki 页面。
点亮屏幕
可以使用这个简单的程序,测试能否在 OLED 上正常显示信息:
#include <U8g2lib.h>
#define OLED_SDA 1
#define OLED_SCL 0
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R2, OLED_SCL, OLED_SDA, U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0,10,"Hello World!");
u8g2.sendBuffer();
delay(1000);
}
MQTT 代理服务
MQTT 是一种基于客户端 - 服务器的消息发布 / 订阅传输协议。在本项目中,MQTT 是服务器与 ESP32 通信的桥梁,为了方便,我将 MQTT 服务部署在需要监控的服务器上;如果有需要,你也可以部署在其他的机器上。
部署 Mosquitto 服务
Mosquitto 是一款实现了消息推送协议 MQTT v3.1 的开源消息代理软件,在这里我用的是 Docker 方式部署的 eclipse-mosquitto 作为 MQTT 代理服务器。如果不熟悉 Docker 部署的方式,可以参考文章 Docker 简易指南 与 Docker Compose - 更优雅的打开方式。
根据官方的说明,首先需要创建以下目录和文件供 Mosquitto 使用,并赋予足够的权限:(请将 ${STACK_DIR}
修改为本地存放数据的路径,例如 /DATA/AppData/mosquitto
,下文同)
mkdir -vp ${STACK_DIR}/{config,data,log} \
&& touch ${STACK_DIR}/config/mosquitto.conf \
&& chmod -R 755 ${STACK_DIR} \
&& chmod -R 777 ${STACK_DIR}/log \
随后,在 mosquitto.conf
文件中写入以下内容:
persistence true
persistence_location /mosquitto/data
log_dest file /mosquitto/log/mosquitto.log
# 关闭匿名模式
allow_anonymous false
# 指定密码文件
password_file /mosquitto/config/pwfile.conf
使用 docker-compose
方式部署容器:
version: "3"
services:
mosquitto:
container_name: mosquitto_app
image: eclipse-mosquitto:1.6.14 # 2.x 版本可能兼容性不佳
ports:
- "1883:1883"
- "9001:9001"
volumes:
- ${STACK_DIR}/config/mosquitto.conf:/mosquitto/config/mosquitto.conf
- ${STACK_DIR}/data:/mosquitto/data
- ${STACK_DIR}/log:/mosquitto/log
#privileged: true
restart: always
进入容器并修改密码:
cd 存放docker-compose.yml的路径
docker compose up
docker compose ps # 找到运行的容器的ID
docker exec -it 容器ID sh # 进入容器 shell
touch /mosquitto/config/pwfile.conf
chmod -R 755 /mosquitto/config/pwfile.conf
# 创建用户与密码,用户名:test,密码:123
mosquitto_passwd -b /mosquitto/config/pwfile.conf test 123
exit # 退出容器 shell
docker restart 容器ID # 重启容器生效
测试 MQTT 服务器的可用性
正常启动了 mosquitto
服务后,我们可以使用 MQTTBox 测试 MQTT 代理服务器的可用性。
安装软件后,点击 Create MQTT Client
新建连接,按照下图填写相关参数:
其中,HOST
为 MQTT 服务所在的服务器的地址(例如,我的服务器在局域网内的地址是 192.168.1.2
);用户名和密码需要与上文配置 Mosquitto 时设置的值对应。
点击 Save
保存后,如果在顶部状态栏看到绿色的 Connected
,则表示已经连接上服务器。
服务端监控脚本
我们可通过在服务端运行以下 Python 程序,实现对设备实时信息的抓取,并推送到 MQTT 服务器的相应主题上。首先需要安装以下依赖包:
pip install paho-mqtt psutil
创建并运行 Python 程序:
import paho.mqtt.client as mqtt
import psutil
import time
# 连接到 MQTT 代理服务器
client = mqtt.Client()
client.username_pw_set("MQTT用户名", "MQTT密码")
client.connect("MQTT服务器地址", 端口号)
# 例:client.connect("192.168.1.2", 1883)
# 收集服务器状态并发送到 MQTT 主题
while True:
client.publish("USAGE_CPU", psutil.cpu_percent())
client.publish("USAGE_MEM", psutil.virtual_memory().percent)
client.publish("USAGE_DISK", psutil.disk_usage('/').percent)
time.sleep(1) # 每隔一秒发布一次
成功运行后,我们可以在 MQTTBox 顶部状态栏上点击 Add subscriber
添加对这三个主题的订阅,例如:
如果一切正常的话,应该可以在 MQTTBox 中看到不断回传的服务器的状态信息。
Beetle ESP32-C3 显示端
创建以下 Arduino 代码,修改其中的参数,并烧录进 ESP32。如果一切正常的话,应该可以看到不断更新的状态信息。
#include <Wire.h>
#include <U8g2lib.h>
#include <WiFi.h>
#include <PubSubClient.h>
// 使用软件 I2C 方式连接 OLED,重新定义引脚
#define OLED_SDA 1
#define OLED_SCL 0
// MQTT 定义
#define WIFI_SSID "Wi-Fi名称"
#define WIFI_PASSWORD "Wi-Fi密码"
#define MQTT_BROKER "MQTT服务器地址" // 例如 192.168.31.2
#define MQTT_PORT MQTT端口 //例如 1883
#define MQTT_USERNAME "MQTT用户名" //test,应与上文配置对应
#define MQTT_PASSWORD "MQTT密码" //123,应与上文配置对应
#define MQTT_TOPIC_CPU "USAGE_CPU" //订阅的主题
#define MQTT_TOPIC_MEM "USAGE_MEM"
#define MQTT_TOPIC_DISK "USAGE_DISK"
char msg_cpu_usage[10];
char msg_mem_usage[10];
char msg_disk_usage[10];
// 定义 OLED 屏幕对象
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R2, OLED_SCL, OLED_SDA, U8X8_PIN_NONE);
// WIFI 客户端对象
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
// MQTT 回调函数
void mqttCallback(char* topic, byte* payload, unsigned int length) {
if (strcmp(topic, MQTT_TOPIC_CPU) == 0) {
// 记录 CPU 使用率
for (int i = 0; i < length; i++)
msg_cpu_usage = (char)payload;
} else if (strcmp(topic, MQTT_TOPIC_MEM) == 0) {
// 记录内存使用率
for (int i = 0; i < length; i++)
msg_mem_usage = (char)payload;
} else if (strcmp(topic, MQTT_TOPIC_DISK) == 0) {
// 记录磁盘使用率
for (int i = 0; i < length; i++)
msg_disk_usage = (char)payload;
}
}
void setup() {
u8g2.begin(); // 初始化 OLED 屏幕
Wire.begin(); // 开始 I2C 传输
// 连接 WIFI
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}
// 连接 MQTT 代理服务器
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
mqttClient.setCallback(mqttCallback);
if (mqttClient.connect("ESP32", MQTT_USERNAME, MQTT_PASSWORD)) {
mqttClient.subscribe(MQTT_TOPIC_CPU);
mqttClient.subscribe(MQTT_TOPIC_MEM);
mqttClient.subscribe(MQTT_TOPIC_DISK);
}
}
void loop() {
mqttClient.loop(); // 处理 MQTT 消息
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_9x15_tf);
// 显示 CPU 使用率
u8g2.setCursor(0, 12);
u8g2.print("CPU: ");
for (int i = 0; i < 9; i++)
u8g2.print(msg_cpu_usage);
u8g2.print(" %");
// 显示内存使用率
u8g2.setCursor(0, 35);
u8g2.print("Mem: ");
for (int i = 0; i < 9; i++)
u8g2.print(msg_mem_usage);
u8g2.print(" %");
// 显示磁盘使用率
u8g2.setCursor(0, 58);
u8g2.print("Disk: ");
for (int i = 0; i < 9; i++)
u8g2.print(msg_disk_usage);
u8g2.print(" %");
} while (u8g2.nextPage());
}
更多扩展玩法
以下的想法有待实现:
- 增加电池和 3D 打印的外壳,做成更精致的桌面小摆件
- 增加内网穿透,打造为小挂件,不在家也可观察服务器状态
- 将 Python 监测程序封装为 Docker 方式部署
- 优化 UI 布局,实现更多参数监控
- 增加多服务器状态监控功能
- 增加某些参数超出阈值报警的功能
参考与致谢
Post by Power (wiki-power.com)