智能配电箱监控系统开发笔记
一、项目概述
这是一个基于多线程邮箱通信机制的智能配电箱监控系统,实现了以下功能:
-
多线程通信:使用自定义邮箱系统进行线程间通信
-
数据库存储:使用SQLite实时存储传感器数据
-
报警功能:实时监测电压、电流、温度等参数,触发报警
-
图形显示:使用FrameBuffer在屏幕上显示监控界面
二、核心技术原理
2.1 线程邮箱通信机制
// 核心数据结构
typedef struct {
char name_of_sender[32]; // 发送者名称
char data[256]; // 数据内容
} MAIL_DATA;
typedef struct {
// 邮箱系统相关数据结构
} MBS;
// 核心API函数
MBS* create_mail_box_system(); // 创建邮箱系统
void register_to_mail_system(MBS* mbs, const char* name, void* (*func)(void*)); // 注册线程
int send_msg(MBS* mbs, const char* to_name, MAIL_DATA* data); // 发送消息
int recv_msg(MBS* mbs, MAIL_DATA* data); // 接收消息
void wait_all_end(MBS* mbs); // 等待所有线程结束
void destroy_mail_box_system(MBS* mbs); // 销毁邮箱系统
2.2 工作流程
数据线程(data_th) → 生成传感器数据
↓ (通过邮箱发送)
显示线程(show_th) → 在屏幕上显示数据
↓ (通过邮箱发送)
数据库 → 保存历史数据
↓ (检查阈值)
报警系统 → 触发报警记录
三、系统架构设计
3.1 线程设计
1. 显示线程 (show_th)
功能 :接收传感器数据并显示在屏幕上
特点:
-
使用FrameBuffer直接写屏
-
支持中文字符显示
-
实时更新监控界面
2. 数据线程 (data_th)
功能 :生成模拟传感器数据
特点:
-
模拟真实电压、电流、温度波动
-
支持随机时间间隔
-
自动触发报警检测
3.2 数据库设计
数据表结构
-- 传感器数据表
CREATE TABLE sensor_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time DATETIME DEFAULT CURRENT_TIMESTAMP,
voltage REAL, -- 电压值 (单位: V)
current REAL, -- 电流值 (单位: A)
temperature REAL -- 温度值 (单位: °C)
);
-- 报警记录表
CREATE TABLE alarm_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time DATETIME DEFAULT CURRENT_TIMESTAMP,
alarm_type TEXT, -- 报警类型: '过压'/'欠压'/'过流'/'高温'
value REAL, -- 实际测量值
threshold REAL -- 报警阈值
);
四、最终代码实现
cpp
#include "framebuffer.h"
#include "head.h"
#include "linkque.h"
#include "list.h"
#include "mailbox.h"
#include "utf.h"
#include <fcntl.h>
#include <pthread.h>
#include <signal.h> // 信号头文件
#include <sqlite3.h> // 数据库头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// ========== 全局变量 ==========
MBS *g_mbs; // 邮箱系统句柄
int g_running = 1; // 程序运行标志
sqlite3 *g_db = NULL; // 数据库句柄
// ========== 信号处理 ==========
void signal_handler(int sig) {
printf("\n收到停止信号,正在退出...\n");
g_running = 0;
}
// ========== 数据库初始化 ==========
void init_database() {
int rc = sqlite3_open("sensor.db", &g_db);
if (rc != SQLITE_OK) {
printf("数据库打开失败\n");
return;
}
// 创建两个表:传感器数据表和报警记录表
char *sql =
// 第一个表:传感器数据表
"CREATE TABLE IF NOT EXISTS sensor_data ("
"id INTEGER PRIMARY KEY AUTOINCREMENT," // 第1列:自动编号(1,2,3...)
"time DATETIME DEFAULT CURRENT_TIMESTAMP," // 第2列:自动记录时间
"voltage REAL," // 第3列:电压值(小数)
"current REAL," // 第4列:电流值(小数)
"temperature REAL" // 第5列:温度值(小数)
");"
// 第二个表:报警记录表
"CREATE TABLE IF NOT EXISTS alarm_log ("
"id INTEGER PRIMARY KEY AUTOINCREMENT," // 第1列:自动编号
"time DATETIME DEFAULT CURRENT_TIMESTAMP," // 第2列:报警时间
"alarm_type TEXT," // 第3列:报警类型(文字,如"高温")
"value REAL," // 第4列:实际测量值(如实际温度45)
"threshold REAL" // 第5列:报警阈值(如温度阈值40)
");";
char *err_msg = 0;
rc = sqlite3_exec(g_db, sql, 0, 0, &err_msg);
if (rc != SQLITE_OK) {
printf("创建表失败: %s\n", err_msg);
sqlite3_free(err_msg);
}
}
// ========== 保存传感器数据到数据库 ==========
void save_to_database(float voltage, float current, float temp) {
char sql[256];
snprintf(sql, sizeof(sql),
"INSERT INTO sensor_data (voltage, current, temperature) "
"VALUES (%.2f, %.2f, %.2f);",
voltage, current, temp);
// 执行插入命令
sqlite3_exec(g_db, sql, 0, 0, NULL);
}
// ========== 保存报警记录到数据库 ==========
void save_alarm_to_database(const char *alarm_type, float value,
float threshold) {
char sql[256];
snprintf(sql, sizeof(sql),
"INSERT INTO alarm_log (alarm_type, value, threshold) "
"VALUES ('%s', %.2f, %.2f);",
alarm_type, value, threshold);
sqlite3_exec(g_db, sql, 0, 0, NULL);
}
// ========== 显示线程函数 ==========
void *show_th(void *arg) {
MAIL_DATA data; // 创建一个邮件变量
// 初始化UTF-8字库
UTF8_INFO utf8_info;
bzero(&utf8_info, sizeof(UTF8_INFO));
strcpy(utf8_info.path, ZIKUK_FILE_BIG);
utf8_info.width = 32;
utf8_info.height = 32;
init_utf8(&utf8_info);
// 初始化FrameBuffer
int fb_fd = init_fb("/dev/fb0");
if (fb_fd == -1) {
printf("FrameBuffer初始化失败\n");
uninit_utf8(&utf8_info); // 清理字库
return NULL;
}
while (g_running) {
bzero(&data, sizeof(MAIL_DATA)); // 清空邮件内容
// 接收邮件,ret=0表示有邮件,ret=1表示没邮件
int ret = recv_msg(g_mbs, &data);
if (1 == ret) {
usleep(100000); // 100毫秒,不要用sleep(1)
continue;
}
// 显示位置(每次重置)
int x_pos = 50;
int y_pos = 50;
int line_height = 40;
// 清屏为黑色
draw_clear(0x000000);
// 显示标题
draw_utf8_str(&utf8_info, x_pos, y_pos, "智能配电箱监控系统",
0xFF0000, 0x000000);
y_pos += line_height;
// 显示传感器数据
draw_utf8_str(&utf8_info, x_pos, y_pos, data.data,
0xFFFFFF, 0x000000);
y_pos += line_height;
// 显示时间(绿色字)
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[32];
strftime(time_str, sizeof(time_str), "时间: %Y-%m-%d %H:%M:%S", tm_info);
draw_utf8_str(&utf8_info, x_pos, y_pos, time_str,
0x00FF00, 0x0000FF);
// 绘制背景图片
draw_bmp(0, 0, "./res/1.bmp", 800, 600);
}
// 清理显示资源
if (fb_fd != -1) {
uninit_utf8(&utf8_info);
uninit_fb(fb_fd);
}
printf("显示线程退出\n");
return NULL;
}
// ========== 数据生成线程函数 ==========
void *data_th(void *arg) {
// 设置随机种子
srand(time(NULL));
MAIL_DATA data;
int data_count = 0;
while (g_running) {
// 生成传感器数据(模拟真实波动)
float voltage = 220.0 + (rand() % 21 - 10); // 210-230V
float current = 5.0 + (rand() % 31) * 0.1; // 5-8A
float temperature = 25.0 + (rand() % 21); // 25-45°C
// 保存到数据库
save_to_database(voltage, current, temperature);
// 检查报警条件
int has_alarm = 0;
// 电压报警检查
if (voltage > 235.0) {
save_alarm_to_database("过压", voltage, 235.0);
has_alarm = 1;
printf("⚠️ 过压报警: %.1fV > 235.0V\n", voltage);
}
if (voltage < 200.0) {
save_alarm_to_database("欠压", voltage, 200.0);
has_alarm = 1;
printf("⚠️ 欠压报警: %.1fV < 200.0V\n", voltage);
}
// 电流报警检查
if (current > 10.0) {
save_alarm_to_database("过流", current, 10.0);
has_alarm = 1;
printf("⚠️ 过流报警: %.1fA > 10.0A\n", current);
}
// 温度报警检查
if (temperature > 60.0) {
save_alarm_to_database("高温", temperature, 60.0);
has_alarm = 1;
printf("⚠️ 高温报警: %.1f°C > 60.0°C\n", temperature);
}
// 发送给显示线程
bzero(&data, sizeof(data)); // 清空邮件内容
sprintf(data.data, "电压:%.1fV 电流:%.1fA 温度:%.1f°C",
voltage, current, temperature);
// 设置发送者名称
strcpy(data.name_of_sender, "data");
// 发送给show线程
send_msg(g_mbs, "show", &data);
// 随机等待0-2秒
sleep(rand() % 3);
data_count++;
if (data_count % 10 == 0) {
printf("✅ 已生成 %d 条传感器数据\n", data_count);
}
}
printf("数据线程退出\n");
return NULL;
}
// ========== 主函数 ==========
int main(int argc, char **argv) {
printf("=======================================\n");
printf(" 智能配电箱监控系统 v1.0\n");
printf("=======================================\n");
// 设置信号处理
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// 初始化数据库
printf("初始化数据库...\n");
init_database();
// 创建邮箱系统
printf("创建邮箱系统...\n");
g_mbs = create_mail_box_system();
if (!g_mbs) {
printf("创建邮箱系统失败!\n");
return -1;
}
// 注册线程到邮箱系统
printf("注册显示线程...\n");
register_to_mail_system(g_mbs, "show", show_th);
printf("注册数据线程...\n");
register_to_mail_system(g_mbs, "data", data_th);
printf("\n系统启动完成!\n");
printf("运行中...按 Ctrl+C 停止\n");
printf("=======================================\n\n");
// 等待所有线程结束
wait_all_end(g_mbs);
// 清理资源
printf("\n清理资源...\n");
if (g_db) {
sqlite3_close(g_db);
printf("数据库已关闭\n");
}
destroy_mail_box_system(g_mbs);
printf("邮箱系统已销毁\n");
printf("程序安全退出\n");
return 0;
}
五、编译和运行说明
5.1 编译命令
# 需要链接的库:pthread, sqlite3
gcc -o smart_powerbox main.c mailbox.c linkque.c list.c framebuffer.c utf.c \
-lpthread -lsqlite3 -lm
5.2 运行说明
# 1. 运行程序
./smart_powerbox
# 2. 查看数据库(另一个终端)
sqlite3 sensor.db
# 3. 在sqlite3中查看数据
sqlite> SELECT * FROM sensor_data;
sqlite> SELECT * FROM alarm_log;
sqlite> .exit
六、技术要点总结
6.1 多线程通信优势
-
解耦:各线程独立工作,通过邮箱通信
-
实时性:数据生成和显示分离,互不影响
-
扩展性:方便添加新功能模块
6.2 数据库设计要点
-
时间戳:自动记录数据时间
-
数据类型:使用REAL存储浮点数
-
报警记录:单独建表,便于查询分析
6.3 显示技术要点
-
FrameBuffer:直接操作显示缓冲区
-
UTF-8支持:显示中文字符
-
定时刷新:实时更新显示内容
6.4 报警逻辑
-
阈值设置:根据实际需求设定
-
实时检测:每次生成数据时检查
-
记录保存:所有报警都有历史记录
七、扩展建议
7.1 功能扩展
-
增加配置文件:支持参数配置
-
添加日志系统:记录程序运行状态
-
支持远程访问:添加Web界面
7.2 性能优化
-
数据库优化:使用事务批量插入
-
内存管理:合理控制内存使用
-
线程调度:优化线程优先级
八、注意事项
-
权限问题:FrameBuffer操作需要root权限
-
资源清理:确保所有资源正确释放
-
错误处理:添加更完善的错误处理机制
-
信号安全:确保信号处理函数可重入
这个系统展示了如何将多线程、数据库、图形显示等技术综合应用,构建一个完整的嵌入式监控系统。