Files
xInfo/main/DisplayManager.cpp
T
2026-08-31 15:05:13 +08:00

276 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file DisplayManager.cpp
* @brief 环境监测与网络信息墨水屏 UI 绘制实现。
*
* 屏幕:400×300,通过 CONFIG_DISPLAY_BWR / CONFIG_DISPLAY_BW 编译宏
* 区分三色(黑/白/红)和纯黑白两种模式。
* 英文字体:Font8, Font12, Font16, Font20, Font24
* 中文字体:Font16CN(Unifont 16×16)
*/
#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] = {0};
EPD_Display(ImageBW, emptyRW);
#endif
EPD_DeepSleep();
}
void DisplayManager::updateFastAndSleep() {
ESP_LOGI(TAG, "快速刷新墨水屏...");
EPD_HW_Init_Fast();
#ifdef CONFIG_DISPLAY_BWR
EPD_WhiteScreen_ALL_Fast(ImageBW, ImageRW);
#else
static unsigned char emptyRW[EPD_ARRAY] = {0};
EPD_WhiteScreen_ALL_Fast(ImageBW, emptyRW);
#endif
EPD_DeepSleep();
}
void DisplayManager::switchColor(bool toRed) {
#ifdef CONFIG_DISPLAY_BWR
Paint_SelectImage(toRed ? ImageRW : ImageBW);
#else
(void)toRed;
Paint_SelectImage(ImageBW);
#endif
}
// =========================================================================
// 顶部横幅:标题 + WiFi 状态
// Y: 0 ~ 28
// =========================================================================
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 \xe7\x8e\xaf\xe5\xa2\x83\xe7\x9b\x91\xe6\xb5\x8b\xe7\xbb\x88\xe7\xab\xaf", Font16CN, WHITE, RED); // xInfo 环境监测终端
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 \xe7\x8e\xaf\xe5\xa2\x83\xe7\x9b\x91\xe6\xb5\x8b\xe7\xbb\x88\xe7\xab\xaf", Font16CN, WHITE, BLACK); // xInfo 环境监测终端
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
}
// =========================================================================
// 核心温湿度看板区
// Y: 32 ~ 148
// =========================================================================
void DisplayManager::drawThermoHygrometer(const DashboardData &data) {
char buf[32];
// 更新时间(靠右显示)
if (!data.lastReport.empty()) {
char timeStr[48];
snprintf(timeStr, sizeof(timeStr), "\xe6\x9b\xb4\xe6\x96\xb0: %s", data.lastReport.c_str()); // 更新:
Paint_DrawString_UTF8(290, 34, timeStr, Font16CN, BLACK, WHITE);
}
// 左侧:室内温度看板 (X: 10 ~ 190)
Paint_DrawString_UTF8(15, 36, "\xe5\xae\xa4\xe5\x86\x85\xe6\xb8\xa9\xe5\xba\xa6", 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, "[\xe5\x81\x8f\xe5\x87\x89]", Font16CN, BLACK, WHITE); // [偏凉]
} else if (tempVal <= 26.0f) {
Paint_DrawString_UTF8(15, 115, "[\xe8\x88\x92\xe9\x80\x82]", Font16CN, BLACK, WHITE); // [舒适]
} else {
Paint_DrawString_UTF8(15, 115, "[\xe5\x81\x8f\xe7\x83\xad]", 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, "\xe7\x9b\xb8\xe5\xaf\xb9\xe6\xb9\xbf\xe5\xba\xa6", 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, "[\xe5\xb9\xb2\xe7\x87\xa5]", Font16CN, BLACK, WHITE); // [干燥]
} else if (humiVal <= 70.0f) {
Paint_DrawString_UTF8(215, 115, "[\xe9\x80\x82\xe5\xae\x9c]", Font16CN, BLACK, WHITE); // [适宜]
} else {
Paint_DrawString_UTF8(215, 115, "[\xe6\xbd\xae\xe6\xb9\xbf]", 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(10, 156, "\xe3\x80\x90 \xe7\xa9\xba\xe6\xb0\x94\xe8\xb2\xb4\xe9\x87\x8f\xe6\x8c\x87\xe6\xa0\x87 \xe3\x80\x91", 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, "\xe4\xbc\x98", Font16CN, BLACK, WHITE); // 优
} else if (co2Val <= 1200.0f) {
Paint_DrawString_UTF8(15, 236, "\xe8\x89\xaf", Font16CN, BLACK, WHITE); // 良
} else {
Paint_DrawString_UTF8(15, 236, "\xe9\x9c\x80\xe9\x80\x9a\xe9\xa3\x8e", 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, "\xe6\xad\xa3\xe5\xb8\xb8", Font16CN, BLACK, WHITE); // 正常
} else {
Paint_DrawString_UTF8(145, 236, "\xe8\xb6\x85\xe6\xa0\x87", 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, "\xe5\xae\x89\xe5\x85\xa8", Font16CN, BLACK, WHITE); // 安全
} else {
Paint_DrawString_UTF8(275, 236, "\xe8\xb6\x85\xe6\xa0\x87", 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, "\xe7\xb3\xbb\xe7\xbb\x9f\xe5\xbc\x82\xe5\xb8\xb8", Font16CN, BLACK, WHITE); // 系统异常
Paint_DrawString_EN(10, 120, errorMsg.c_str(), &Font16, BLACK, WHITE);
Paint_DrawString_UTF8(10, 150, "\xe5\xb0\x86\xe5\x9c\xa8\xe4\xb8\x8b\xe4\xb8\xaa\xe5\x91\xa8\xe6\x9c\x9f\xe9\x87\x8d\xe8\xaf\x95...", Font16CN, BLACK, WHITE); // 将在下个周期重试...
}