269 lines
10 KiB
C++
269 lines
10 KiB
C++
/**
|
||
* @file DisplayManager.cpp
|
||
* @brief 环境监测与网络信息墨水屏 UI 绘制实现。
|
||
*/
|
||
#include "DisplayManager.h"
|
||
#include "ChineseFont.h"
|
||
#include "EPD.h"
|
||
#include "EPD_SPI.h"
|
||
#include "GUI_Paint.h"
|
||
#include "esp_log.h"
|
||
#include <cstdio>
|
||
#include <cstring>
|
||
#include <cstdlib>
|
||
|
||
static const char *TAG = "DisplayManager";
|
||
|
||
// 帧缓冲区
|
||
static unsigned char ImageBW[EPD_ARRAY]; ///< 黑白像素缓冲
|
||
#ifdef CONFIG_DISPLAY_BWR
|
||
static unsigned char ImageRW[EPD_ARRAY]; ///< 红白像素缓冲(仅 BWR 模式使用)
|
||
#endif
|
||
|
||
DisplayManager::DisplayManager() {}
|
||
DisplayManager::~DisplayManager() {}
|
||
|
||
void DisplayManager::initHardware() {
|
||
EPD_SPI_Init();
|
||
}
|
||
|
||
void DisplayManager::beginDrawing() {
|
||
Paint_NewImage(ImageBW, EPD_WIDTH, EPD_HEIGHT, 0, WHITE);
|
||
Paint_SelectImage(ImageBW);
|
||
Paint_Clear(WHITE);
|
||
|
||
#ifdef CONFIG_DISPLAY_BWR
|
||
Paint_NewImage(ImageRW, EPD_WIDTH, EPD_HEIGHT, 0, WHITE);
|
||
Paint_SelectImage(ImageRW);
|
||
Paint_Clear(WHITE);
|
||
|
||
Paint_SelectImage(ImageBW);
|
||
#endif
|
||
}
|
||
|
||
void DisplayManager::updateFullAndSleep() {
|
||
#ifdef CONFIG_DISPLAY_BWR
|
||
ESP_LOGI(TAG, "全量三色刷新墨水屏...");
|
||
EPD_HW_Init_GUI();
|
||
EPD_Display(ImageBW, ImageRW);
|
||
#else
|
||
ESP_LOGI(TAG, "全量黑白刷新墨水屏...");
|
||
EPD_HW_Init_GUI();
|
||
static unsigned char emptyRW[EPD_ARRAY];
|
||
memset(emptyRW, 0xFF, EPD_ARRAY);
|
||
EPD_Display(ImageBW, emptyRW);
|
||
#endif
|
||
EPD_DeepSleep();
|
||
}
|
||
|
||
void DisplayManager::updateFastAndSleep() {
|
||
ESP_LOGI(TAG, "快速无闪烁刷新墨水屏(局部波形)...");
|
||
#ifdef CONFIG_DISPLAY_BWR
|
||
// 三色模式下理论上不支持无闪烁,退化处理
|
||
EPD_HW_Init_Fast();
|
||
EPD_WhiteScreen_ALL_Fast(ImageBW, ImageRW);
|
||
#else
|
||
// 【终极抗闪烁及死锁修复】
|
||
EPD_HW_Init_GUI();
|
||
|
||
// 修复导致硬件死锁在 Epaper_READBUSY() 的传参错误 Bug:
|
||
// 原厂 EPD_Dis_Part 函数的签名中:
|
||
// PART_COLUMN 代表的是 Y 轴(Height,需传 300)
|
||
// PART_LINE 代表的是 X 轴像素宽(Width,需传 400,内部会除以 8)
|
||
// 之前长宽传反,越界写寄存器触碰了驱动硬件的红线,导致永远不拉高 Busy 引脚
|
||
EPD_Dis_Part(0, 0, ImageBW, EPD_HEIGHT, EPD_WIDTH);
|
||
#endif
|
||
EPD_DeepSleep();
|
||
}
|
||
|
||
void DisplayManager::switchColor(bool toRed) {
|
||
#ifdef CONFIG_DISPLAY_BWR
|
||
Paint_SelectImage(toRed ? ImageRW : ImageBW);
|
||
#else
|
||
(void)toRed;
|
||
Paint_SelectImage(ImageBW);
|
||
#endif
|
||
}
|
||
|
||
void DisplayManager::drawTopBanner(const std::string &networkStatus) {
|
||
#ifdef CONFIG_DISPLAY_BWR
|
||
switchColor(true);
|
||
Paint_DrawRectangle(0, 0, EPD_WIDTH, 26, RED, DRAW_FILL_FULL, DOT_PIXEL_1X1);
|
||
Paint_DrawString_UTF8(10, 5, "xInfo 环境监测终端", Font16CN, WHITE, RED);
|
||
|
||
char buf[32];
|
||
snprintf(buf, sizeof(buf), "[%s]", networkStatus.c_str());
|
||
int strW = static_cast<int>(strlen(buf)) * 8;
|
||
int offsetX = EPD_WIDTH - strW - 10;
|
||
if (offsetX < 220) offsetX = 220;
|
||
Paint_DrawString_EN(offsetX, 8, buf, &Font12, WHITE, RED);
|
||
|
||
switchColor(false);
|
||
#else
|
||
Paint_DrawRectangle(0, 0, EPD_WIDTH, 26, BLACK, DRAW_FILL_FULL, DOT_PIXEL_1X1);
|
||
Paint_DrawString_UTF8(10, 5, "xInfo 环境监测终端", Font16CN, WHITE, BLACK);
|
||
|
||
char buf[32];
|
||
snprintf(buf, sizeof(buf), "[%s]", networkStatus.c_str());
|
||
int strW = static_cast<int>(strlen(buf)) * 8;
|
||
int offsetX = EPD_WIDTH - strW - 10;
|
||
if (offsetX < 220) offsetX = 220;
|
||
Paint_DrawString_EN(offsetX, 8, buf, &Font12, WHITE, BLACK);
|
||
#endif
|
||
}
|
||
|
||
void DisplayManager::drawThermoHygrometer(const DashboardData &data) {
|
||
char buf[32];
|
||
|
||
if (!data.lastReport.empty()) {
|
||
char timeStr[48];
|
||
snprintf(timeStr, sizeof(timeStr), "更新: %s", data.lastReport.c_str());
|
||
Paint_DrawString_UTF8(290, 34, timeStr, Font16CN, BLACK, WHITE);
|
||
}
|
||
|
||
Paint_DrawString_UTF8(15, 36, "室内温度", Font16CN, BLACK, WHITE);
|
||
if (!data.temperature.empty()) {
|
||
snprintf(buf, sizeof(buf), "%s", data.temperature.c_str());
|
||
Paint_DrawString_EN(15, 68, buf, &Font24, BLACK, WHITE);
|
||
int numW = static_cast<int>(strlen(buf)) * 17 + 12;
|
||
Paint_DrawCircle(numW + 8, 72, 3, BLACK, DRAW_FILL_EMPTY, DOT_PIXEL_1X1);
|
||
Paint_DrawString_EN(numW + 15, 70, "C", &Font20, BLACK, WHITE);
|
||
|
||
// 舒适度判断小标签
|
||
float tempVal = strtof(data.temperature.c_str(), nullptr);
|
||
if (tempVal < 18.0f) {
|
||
Paint_DrawString_UTF8(15, 115, "[偏凉]", Font16CN, BLACK, WHITE); // [偏凉]
|
||
} else if (tempVal <= 26.0f) {
|
||
Paint_DrawString_UTF8(15, 115, "[舒适]", Font16CN, BLACK, WHITE); // [舒适]
|
||
} else {
|
||
Paint_DrawString_UTF8(15, 115, "[偏热]", Font16CN, BLACK, WHITE); // [偏热]
|
||
}
|
||
} else {
|
||
Paint_DrawString_EN(15, 68, "--.-", &Font24, BLACK, WHITE);
|
||
}
|
||
|
||
// 中间竖向分隔线
|
||
Paint_DrawLine(200, 36, 200, 142, BLACK, LINE_STYLE_SOLID, DOT_PIXEL_1X1);
|
||
|
||
// 右侧:相对湿度看板 (X: 210 ~ 390)
|
||
Paint_DrawString_UTF8(215, 36, "相对湿度", Font16CN, BLACK, WHITE); // 相对湿度
|
||
if (!data.humidity.empty()) {
|
||
snprintf(buf, sizeof(buf), "%s%%", data.humidity.c_str());
|
||
Paint_DrawString_EN(215, 68, buf, &Font24, BLACK, WHITE);
|
||
|
||
// 湿度状态小标签
|
||
float humiVal = strtof(data.humidity.c_str(), nullptr);
|
||
if (humiVal < 40.0f) {
|
||
Paint_DrawString_UTF8(215, 115, "[干燥]", Font16CN, BLACK, WHITE); // [干燥]
|
||
} else if (humiVal <= 70.0f) {
|
||
Paint_DrawString_UTF8(215, 115, "[适宜]", Font16CN, BLACK, WHITE); // [适宜]
|
||
} else {
|
||
Paint_DrawString_UTF8(215, 115, "[潮湿]", Font16CN, BLACK, WHITE); // [潮湿]
|
||
}
|
||
} else {
|
||
Paint_DrawString_EN(215, 68, "--.-%", &Font24, BLACK, WHITE);
|
||
}
|
||
}
|
||
|
||
// =========================================================================
|
||
// 空气质量指标多维度展示区
|
||
// Y: 152 ~ 265
|
||
// =========================================================================
|
||
void DisplayManager::drawAirQuality(const DashboardData &data) {
|
||
// 横向分隔线
|
||
Paint_DrawLine(10, 150, EPD_WIDTH - 10, 150, BLACK, LINE_STYLE_SOLID, DOT_PIXEL_1X1);
|
||
|
||
// 区域标题
|
||
Paint_DrawString_UTF8(15, 156, "\xe7\xa9\xba\xe6\xb0\x94\xe8\xb4\xa8\xe9\x87\x8f\xe6\x8c\x87\xe6\xa0\x87", Font16CN, BLACK, WHITE); // 【 空气质量指标 】
|
||
|
||
// 第 1 列:CO2 (X: 15 ~ 125)
|
||
Paint_DrawString_EN(15, 180, "CO", &Font16, BLACK, WHITE);
|
||
Paint_DrawString_EN(37, 184, "2", &Font12, BLACK, WHITE);
|
||
Paint_DrawString_EN(47, 182, "(ppm)", &Font12, BLACK, WHITE);
|
||
|
||
if (!data.co2.empty()) {
|
||
Paint_DrawString_EN(15, 204, data.co2.c_str(), &Font20, BLACK, WHITE);
|
||
float co2Val = strtof(data.co2.c_str(), nullptr);
|
||
if (co2Val <= 800.0f) {
|
||
Paint_DrawString_UTF8(15, 236, "优", Font16CN, BLACK, WHITE); // 优
|
||
} else if (co2Val <= 1200.0f) {
|
||
Paint_DrawString_UTF8(15, 236, "良", Font16CN, BLACK, WHITE); // 良
|
||
} else {
|
||
Paint_DrawString_UTF8(15, 236, "需通风", Font16CN, BLACK, WHITE); // 需通风
|
||
}
|
||
} else {
|
||
Paint_DrawString_EN(15, 204, "--", &Font20, BLACK, WHITE);
|
||
}
|
||
|
||
// 列分割线 1
|
||
Paint_DrawLine(135, 180, 135, 255, BLACK, LINE_STYLE_DOTTED, DOT_PIXEL_1X1);
|
||
|
||
// 第 2 列:TVOC (X: 145 ~ 255)
|
||
Paint_DrawString_EN(145, 180, "TVOC", &Font16, BLACK, WHITE);
|
||
Paint_DrawString_EN(193, 182, "(mg/m", &Font12, BLACK, WHITE);
|
||
Paint_DrawString_EN(241, 179, "3", &Font8, BLACK, WHITE);
|
||
Paint_DrawString_EN(248, 182, ")", &Font12, BLACK, WHITE);
|
||
|
||
if (!data.tvoc.empty()) {
|
||
Paint_DrawString_EN(145, 204, data.tvoc.c_str(), &Font20, BLACK, WHITE);
|
||
float tvocVal = strtof(data.tvoc.c_str(), nullptr);
|
||
if (tvocVal <= 0.6f) {
|
||
Paint_DrawString_UTF8(145, 236, "正常", Font16CN, BLACK, WHITE); // 正常
|
||
} else {
|
||
Paint_DrawString_UTF8(145, 236, "超标", Font16CN, BLACK, WHITE); // 超标
|
||
}
|
||
} else {
|
||
Paint_DrawString_EN(145, 204, "--", &Font20, BLACK, WHITE);
|
||
}
|
||
|
||
// 列分割线 2
|
||
Paint_DrawLine(265, 180, 265, 255, BLACK, LINE_STYLE_DOTTED, DOT_PIXEL_1X1);
|
||
|
||
// 第 3 列:CH2O (X: 275 ~ 385)
|
||
Paint_DrawString_EN(275, 180, "CH", &Font16, BLACK, WHITE);
|
||
Paint_DrawString_EN(297, 184, "2", &Font12, BLACK, WHITE);
|
||
Paint_DrawString_EN(307, 180, "O", &Font16, BLACK, WHITE);
|
||
Paint_DrawString_EN(320, 182, "(mg/m", &Font12, BLACK, WHITE);
|
||
Paint_DrawString_EN(368, 179, "3", &Font8, BLACK, WHITE);
|
||
Paint_DrawString_EN(375, 182, ")", &Font12, BLACK, WHITE);
|
||
|
||
if (!data.ch2o.empty()) {
|
||
Paint_DrawString_EN(275, 204, data.ch2o.c_str(), &Font20, BLACK, WHITE);
|
||
float ch2oVal = strtof(data.ch2o.c_str(), nullptr);
|
||
if (ch2oVal <= 0.08f) {
|
||
Paint_DrawString_UTF8(275, 236, "安全", Font16CN, BLACK, WHITE); // 安全
|
||
} else {
|
||
Paint_DrawString_UTF8(275, 236, "超标", Font16CN, BLACK, WHITE); // 超标
|
||
}
|
||
} else {
|
||
Paint_DrawString_EN(275, 204, "--", &Font20, BLACK, WHITE);
|
||
}
|
||
}
|
||
|
||
// =========================================================================
|
||
// 底部网络及硬件信息栏
|
||
// Y: 268 ~ 300
|
||
// =========================================================================
|
||
void DisplayManager::drawBottomInfoBar(const std::string &ip,
|
||
const std::string &mac) {
|
||
Paint_DrawLine(0, EPD_HEIGHT - 25, EPD_WIDTH, EPD_HEIGHT - 25, BLACK,
|
||
LINE_STYLE_SOLID, DOT_PIXEL_1X1);
|
||
|
||
char buf[64];
|
||
snprintf(buf, sizeof(buf), "IP: %s", ip.empty() ? "0.0.0.0" : ip.c_str());
|
||
Paint_DrawString_EN(10, EPD_HEIGHT - 18, buf, &Font12, BLACK, WHITE);
|
||
|
||
snprintf(buf, sizeof(buf), "MAC: %s", mac.c_str());
|
||
int macX = EPD_WIDTH - static_cast<int>(strlen(buf)) * 7 - 10;
|
||
if (macX < 200) macX = 200;
|
||
Paint_DrawString_EN(macX, EPD_HEIGHT - 18, buf, &Font12, BLACK, WHITE);
|
||
}
|
||
|
||
// =========================================================================
|
||
// 错误页面
|
||
// =========================================================================
|
||
void DisplayManager::drawError(const std::string &errorMsg) {
|
||
Paint_DrawString_UTF8(10, 80, "系统异常", Font16CN, BLACK, WHITE); // 系统异常
|
||
Paint_DrawString_EN(10, 120, errorMsg.c_str(), &Font16, BLACK, WHITE);
|
||
Paint_DrawString_UTF8(10, 150, "将在下个周期重试...", Font16CN, BLACK, WHITE); // 将在下个周期重试...
|
||
} |