1. 花了几天时间基于lvgl8.3封装了lvgl_cpp,写了一些基本需要的控件,支持链式调用

还存在一点点bug,不难fix
2. 增加了中文字库,支持中文显示
3. 修复和优化了一些地方
This commit is contained in:
Misaki
2025-09-27 05:43:43 +08:00
parent a47e20cb64
commit 801138631e
18 changed files with 67398 additions and 36 deletions
+1
View File
@@ -5,3 +5,4 @@ log
managed_components managed_components
.idea .idea
.venv .venv
.vscode
-1
View File
@@ -3,7 +3,6 @@
// //
#pragma once #pragma once
#include <string> #include <string>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
+269 -4
View File
@@ -7,6 +7,7 @@
#include "PetBaseClass.h" #include "PetBaseClass.h"
#include "PetDao.h" #include "PetDao.h"
#include <iostream> #include <iostream>
#include <optional>
void testPetSystem() { void testPetSystem() {
std::cout << "Test point1" << std::endl; std::cout << "Test point1" << std::endl;
@@ -431,10 +432,273 @@ void createWebSocket() {
std::thread websocket_thread = ThreadManager::createThread(websocket_config, websocket_task); std::thread websocket_thread = ThreadManager::createThread(websocket_config, websocket_task);
websocket_thread.detach(); websocket_thread.detach();
} }
#include "LVGLRender.h"
#include <lvgl.h>
#define TAG "BIN_TEST"
/* 从 SD 卡读任意 .bin 到堆 → 直接显示 */
void test_show_bin(const char* fileName)
{
ESP_LOGI(TAG, "=== fast bin test: %s ===", fileName);
/* 1. 读文件 */
std::string path = "/sdcard/" + std::string(fileName);
std::string data = SDFileManager::getInstance()->readFileSync(path.c_str());
if (data.empty()) {
ESP_LOGE(TAG, "read fail");
return;
}
size_t sz = data.size();
ESP_LOGI(TAG, "file size = %zu", sz);
ESP_LOG_BUFFER_HEX(TAG, data.data(), 16); // 头 16 字节
/* 2. 拷到堆(LVGL 长期持有)*/
void* buf = heap_caps_malloc(sz, MALLOC_CAP_8BIT);
memcpy(buf, data.data(), sz);
/* 3. 离线量好的尺寸(先填 320×240 测试,不对再改)*/
uint32_t w = 720;
uint32_t h = 720;
if (sz != w * h * 2) { // RGB565 每像素 2 字节
ESP_LOGW(TAG, "size mismatch, expect %ld, got %zu", w * h * 2, sz);
}
/* 4. 一次性描述符 */
static lv_img_dsc_t dsc;
dsc.data = static_cast<const uint8_t *>(buf);
dsc.header.cf = LV_IMG_CF_TRUE_COLOR;
dsc.header.w = w;
dsc.header.h = h;
dsc.data_size = sz;
/* 5. 直接显示(不经过 PNG 解码器)*/
lv_obj_t* img = lv_img_create(lv_scr_act());
lv_img_set_src(img, &dsc);
lv_obj_center(img);
ESP_LOGI(TAG, "bin displayed, w=%ld h=%ld", w, h);
}
#include "lvpp.h"
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan);
// 使用全局智能指针管理主要对象
static std::shared_ptr<lvgl_cpp::Screen> g_screen;
using namespace lvgl_cpp;
struct AppCalc : BaseApp {
using BaseApp::BaseApp;
void onShow() override {
Label(*this).text("Calculator").center();
}
};
struct AppMusic : BaseApp {
using BaseApp::BaseApp;
void onShow() override {
btn_.emplace(*this);
gif.emplace(*this);
btn_->size(180, 60)
.align(LV_ALIGN_CENTER, nullptr, 0, 80)
.on(LV_EVENT_CLICKED, [&](lv_event_t*) {
/* 居中 GIF,背景黑 */
lv_obj_set_style_bg_color(this->raw(), lv_color_black(), 0);
lv_obj_set_style_bg_opa(this->raw(), LV_OPA_COVER, 0);
gif->src("small_-min.gif")
.center();
});
/* 按钮文字 */
lv_obj_t* label = lv_label_create(btn_->raw());
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
lv_label_set_text(label, "Gif测试");
lv_obj_center(label);
}
private:
std::optional<lvgl_cpp::Button> btn_; // c++17 轻量 RAII
std::optional<lvgl_cpp::Gif> gif;
};
class ButtonApp : public BaseApp {
public:
using BaseApp::BaseApp; // 继承 exit 按钮机制
void onShow() override {
/* 居中按钮:180×60 px,圆角,现代蓝 */
btn_.emplace(*this);
btn_->size(180, 60)
.align(LV_ALIGN_CENTER, nullptr, 0, 80)
.on(LV_EVENT_CLICKED, [](lv_event_t*) {
lvgl_cpp::Toast::show("咕咕嘎嘎!",
lvgl_cpp::Toast::Type::INFO,
1000);
});
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan);
/* 按钮文字 */
lv_obj_t* label = lv_label_create(btn_->raw());
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
lv_label_set_text(label, "按我");
lv_obj_center(label);
}
private:
std::optional<lvgl_cpp::Button> btn_; // c++17 轻量 RAII
};
static std::shared_ptr<HomePage> g_home;
static std::shared_ptr<AppMenu> g_menu;
void Cpp_Hand() { void Cpp_Hand() {
// testMIC(); // testMIC();
// testPetSystem(); // testPetSystem();
SDFileManager::getInstance()->tryInitSDCard();
LVGLRender::getInstance()->tryToInitRenderGif(); // 初始化lvgl驱动(包括任务循环)
LVGLRender::setFps(60); // 设置lvgl刷新频率
/* LVGL 已经初始化,屏幕驱动已注册 */
// auto scr = lvgl_cpp::Screen{}; // RAII,离开作用域自动 del
// lv_scr_load(scr.raw()); // 让 LVGL 把它当活动屏幕
// lvgl_cpp::Image img(scr);
// img.bin("pic_no_alp_swap.bin", 720, 720)
// .center();
//
// /* 1. 创建按钮 */
// /* 1. 黑色文字样式 */
// static lvgl_cpp::Style txt_style;
// txt_style.text_color(lv_color_black());
//
// /* 2. 按钮 */
// lvgl_cpp::Button btn{scr};
// btn.size(150, 60).pos(40, 40);
// /* 3. 先创建 Label 对象并保存,再链式调 */
// lvgl_cpp::Label lbl{btn}; // 一定要存实例
// lbl.text("Click Me")
// .add_style(&txt_style.s, LV_PART_MAIN); // 样式作用到文字本身
// /* 3. 注册点击事件 */
// btn.on(LV_EVENT_CLICKED, [](lv_event_t*){
// lvgl_cpp::Toast::show("Saved successfully !");
// /* 2. 警告,3 s,顶部 */
// lvgl_cpp::Toast::show("SD card missing", lvgl_cpp::Toast::Type::WARN, 3000, LV_ALIGN_TOP_MID, 20);
//
// /* 3. 错误,1.5 s,底部右侧 */
// lvgl_cpp::Toast::show("Network error", lvgl_cpp::Toast::Type::ERROR, 1500, LV_ALIGN_BOTTOM_RIGHT, -30);
// ESP_LOGI("BTN", "pressed");
// });
//
// /* 1. 消息框 */
// // const char* btns[] = {"Yes", "No", ""};
// // auto mbox = lvgl_cpp::MsgBox::create("Hint", "Delete file ?", btns);
//
// /* 3. 进度条(双向 + 动画) */
// lvgl_cpp::Bar bar(scr);
// bar.range(0, 100)
// .start_value(20) // 左端
// .value(80, LV_ANIM_ON) // 右端带动画
// .anim_time(500)
// .size(200, 15)
// .align(LV_ALIGN_CENTER, nullptr, 0, 40);
//
// /* 4. 折线:画一个 △ */
// std::vector<lv_point_t> triangle = {{0,0}, {40,0}, {20,40}, {0,0}};
// lvgl_cpp::Line line(scr);
// line.points(triangle)
// .y_invert(false)
// .align(LV_ALIGN_CENTER, nullptr, 0, 100);
//
// lvgl_cpp::Battery bat(scr);
// bat.size(60, 30) // 手机经典尺寸
// .percent(true) // 显示 50%
// .onRead([]() -> uint8_t { // 替换成你的 ADC/INA219 回调
// static uint8_t v = 100;
// if (v) --v;
// return v;
// })
// .align(LV_ALIGN_TOP_RIGHT, -10, 10); // 九宫格对齐
//
// /* 3. 日期时间 */
// lvgl_cpp::DateTime dt(scr);
// dt.format("%m/%d %a %H:%M")
// .onRead([](char* buf, size_t len){
// /* 这里用 SNTP / RTC 填充,示例直接给假时间 */
// snprintf(buf, len, "06/25 Tue 14:30");
// })
// .align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -10);
// lvgl_cpp::ColorPicker cp(scr);
// cp.size(120, 120)
// .align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -20)
// .on(LV_EVENT_VALUE_CHANGED, [&](lv_event_t*){
// lv_color_t c = cp.color();
// ESP_LOGI("CP", "rgb=%d,%d,%d", c.ch.red, c.ch.green_h, c.ch.blue);
// });
// 1. 创建屏幕
g_screen = std::make_shared<lvgl_cpp::Screen>();
lv_scr_load(g_screen->raw());
// 2. 延迟创建界面组件
auto init_timer = std::make_unique<lvgl_cpp::Timer>([&]() {
ESP_LOGI("MAIN", "Initializing UI components...");
// 创建主页
g_home = std::make_shared<HomePage>(*g_screen);
g_home->bg("pic360.bin", 360, 360)
.onBattery([]() -> uint8_t {
static uint8_t v = 100;
if (v) --v;
return 88;
})
.onDateTime([](char* buf, size_t len) {
snprintf(buf, len, "06/25 Tue 14:30");
});
// 创建菜单
g_menu = std::make_shared<AppMenu>(*g_screen);
g_menu->addItem("Calcu", 100, 50)
.addItem("Gif测试", 100, 50)
.addItem("Button", 100, 50)
.onClick([](const char* name) {
ESP_LOGI("APP", "Launching: %s", name);
/* 1. 让工厂创建子应用 */
auto app = AppFactory::create(name, *g_screen);
if (!app) return;
/* 2. 隐藏菜单,显示应用 */
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
app->onShow();
/* 3. 应用退出时回到菜单 */
app->onExit([app_ptr = app.release()]() { // 转移所有权到 lambda
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
lv_obj_del(app_ptr->raw()); // 自毁
});
});
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
// 安全的事件连接
g_home->onOpenMenu([]() {
if (g_home && g_menu) {
lv_obj_add_flag(g_home->raw(), LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
}
});
ESP_LOGI("MAIN", "UI initialization complete");
}, 1000, true); // 1秒后执行一次
// 注册应用
AppFactory::registerApp("Calcu", [](Obj& p) {
return std::make_unique<AppCalc>(p);
});
AppFactory::registerApp("Gif测试", [](Obj& p) {
return std::make_unique<AppMusic>(p);
});
AppFactory::registerApp("Button", [](lvgl_cpp::Obj& p) {
return std::make_unique<ButtonApp>(p);
});
// test_show_bin("pic_no_alp_swap.bin");
// LVGLRender::getInstance()->RenderGif("sequence02mmm.gif");
ESP_LOGI("CppHandle::Cpp_Hand", "当前固件版本 %s:", ToolsClass::getDeviceVersion().c_str()); ESP_LOGI("CppHandle::Cpp_Hand", "当前固件版本 %s:", ToolsClass::getDeviceVersion().c_str());
@@ -442,18 +706,19 @@ void Cpp_Hand() {
ESP_LOGI("CppHandle::Cpp_Hand", "当前设备固件序列号 %s:", ToolsClass::getChipSerialNumber().c_str()); ESP_LOGI("CppHandle::Cpp_Hand", "当前设备固件序列号 %s:", ToolsClass::getChipSerialNumber().c_str());
// 连接wifi // 连接wifi
WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5); // WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5);
// 创建WebSocket // 创建WebSocket
createWebSocket(); // createWebSocket();
// 设置OTA回调 // 设置OTA回调
setupOtaCallbacks(); // setupOtaCallbacks();
while (true) { // 主线程线程循环 while (true) { // 主线程线程循环
// ThreadManager::print_sys_memory(); // 打印系统内存使用情况 // ThreadManager::print_sys_memory(); // 打印系统内存使用情况
// ThreadManager::stats_task(); // 打印任务统计信息 // ThreadManager::stats_task(); // 打印任务统计信息
ESP_LOGI("APP_TASK", "Battery is:%ld", ToolsClass::getInstance()->getBatteryPer());
ESP_LOGI("APP_TASK", "Battery is:%f", ToolsClass::getInstance()->getBatteryVolts());
std::this_thread::sleep_for(sleep_time); // 休眠5秒 std::this_thread::sleep_for(sleep_time); // 休眠5秒
} }
} }
@@ -9,6 +9,7 @@
#include "LVGL_Driver.h" #include "LVGL_Driver.h"
#include <esp_log.h> #include <esp_log.h>
#include <ToolsClass.h>
LVGLRender* LVGLRender::LVGLRenderInstance = nullptr; LVGLRender* LVGLRender::LVGLRenderInstance = nullptr;
std::mutex LVGLRender::instance_mutex; std::mutex LVGLRender::instance_mutex;
@@ -30,12 +31,14 @@ LVGLRender* LVGLRender::getInstance() {
void LVGLRender::LVGL_Update() { void LVGLRender::LVGL_Update() {
while (true) { while (true) {
vTaskDelay(LVGL_DELAY_FROM_FPS(LVGLRender::fps)); {
lv_timer_handler(); const std::lock_guard<std::mutex> lock(mtx); // lvgl并非线程安全,需要加锁
lv_timer_handler();
}
std::this_thread::sleep_for(std::chrono::milliseconds(fps));
} }
} }
// 构造函数 // 构造函数
LVGLRender::LVGLRender() { LVGLRender::LVGLRender() {
ESP_LOGI("LVGL_Render", "LVGL_Render构造函数...初始化媒体驱动..."); ESP_LOGI("LVGL_Render", "LVGL_Render构造函数...初始化媒体驱动...");
@@ -63,14 +66,24 @@ LVGLRender::~LVGLRender() {
} }
void LVGLRender::log() { void LVGLRender::setFps(uint16_t fps_) {
ESP_LOGI("LVGL_Render", "LVGL_Render log..."); fps = fps_;
} }
// 静态:拼出完整路径 uint16_t LVGLRender::getFps() {
std::string LVGLRender::makeFullPath(const std::string& filename) return fps;
{ }
return "/sdcard/" + filename;
void LVGLRender::tryToInitRenderGif() {
ESP_LOGI("LVGL_Render", "尝试初始化 LVGL 底层驱动...");
}
void LVGLRender::log() {
ESP_LOGI("LVGL_Render", "LVGL_Render log...");
} }
#include "SDFileManager.h" #include "SDFileManager.h"
@@ -118,15 +131,15 @@ void LVGLRender::renderGifInternal(const std::vector<uint8_t>& data,
gif_desc.header.cf = LV_IMG_CF_RAW_CHROMA_KEYED; gif_desc.header.cf = LV_IMG_CF_RAW_CHROMA_KEYED;
gif_desc.header.always_zero = 0; gif_desc.header.always_zero = 0;
gif_desc.header.reserved = 0; gif_desc.header.reserved = 0;
gif_desc.header.w = (lv_coord_t)w; gif_desc.header.w = static_cast<lv_coord_t>(w);
gif_desc.header.h = (lv_coord_t)h; gif_desc.header.h = static_cast<lv_coord_t>(h);
gif_desc.data_size = current_gif_data.size(); gif_desc.data_size = current_gif_data.size();
gif_desc.data = current_gif_data.data(); gif_desc.data = current_gif_data.data();
// 创建新的 GIF 对象 // 创建新的 GIF 对象
current_gif_obj = lv_gif_create(lv_scr_act()); current_gif_obj = lv_gif_create(lv_scr_act()); // copy到当前gif对象
lv_gif_set_src(current_gif_obj, &gif_desc); lv_gif_set_src(current_gif_obj, &gif_desc); // 设置源
lv_obj_center(current_gif_obj); lv_obj_center(current_gif_obj); // 居中
ESP_LOGI("LVGLRender", "GIF 已渲染并循环播放"); ESP_LOGI("LVGLRender", "GIF 已渲染并循环播放");
} }
@@ -141,7 +154,7 @@ void LVGLRender::RenderGif(const std::string &filename) {
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0); // 背景黑色 lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0); // 背景黑色
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0); // 透明度 lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0); // 透明度
std::string fullPath = makeFullPath(filename); std::string fullPath = ToolsClass::makeFullPath(filename);
std::vector<uint8_t> gifBin = readWholeFile(fullPath); std::vector<uint8_t> gifBin = readWholeFile(fullPath);
if (gifBin.empty()) return; if (gifBin.empty()) return;
@@ -19,8 +19,6 @@ class LVGLRender {
public: public:
LVGLRender(LVGLRender const&) = delete; // 禁止拷贝构造函数 LVGLRender(LVGLRender const&) = delete; // 禁止拷贝构造函数
LVGLRender& operator=(LVGLRender const&) = delete;// 禁止赋值运算符 LVGLRender& operator=(LVGLRender const&) = delete;// 禁止赋值运算符
public:
static std::string makeFullPath(const std::string& filename);
public: public:
static LVGLRender* getInstance(); static LVGLRender* getInstance();
@@ -32,6 +30,12 @@ public:
*/ */
void RenderGif(const std::string &filename); void RenderGif(const std::string &filename);
static void setFps(uint16_t fps_);
static uint16_t getFps();
void tryToInitRenderGif();
void log(); void log();
// gif渲染 // gif渲染
@@ -59,6 +63,8 @@ private:
lv_obj_t* current_gif_obj = nullptr; /// 当前GIF对象 lv_obj_t* current_gif_obj = nullptr; /// 当前GIF对象
std::vector<uint8_t> current_gif_data; /// 当前GIF数据 std::vector<uint8_t> current_gif_data; /// 当前GIF数据
std::string last_gif_filename; /// 最后一次渲染的GIF文件名 std::string last_gif_filename; /// 最后一次渲染的GIF文件名
std::mutex mtx;
}; };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
//
// Created by misaki on 2025/9/26.
//
#include "lvpp.h"
@@ -0,0 +1,849 @@
//
// Created by misaki on 2025/9/26.
//
#pragma once
#include <esp_heap_caps.h>
#include <esp_log.h>
#include <lvgl.h>
#include <font/lv_font.h>
#include <functional>
#include <optional>
#include <utility>
#include "SDFileManager.h"
#include "ToolsClass.h"
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan); // 思源黑体 Google版
namespace lvgl_cpp {
// 前向声明
class Obj;
class Screen;
class Label;
class Button;
class Arc;
class Slider;
class Switch;
class Roller;
class Dropdown;
class List;
class TextArea;
class Image;
class IconButton;
// 基础封装
class Obj {
public:
Obj() = default;
explicit Obj(lv_obj_t* raw) : ptr_(raw) {}
virtual ~Obj() { if (ptr_) lv_obj_del(ptr_); }
Obj(Obj&& o) noexcept : ptr_(std::exchange(o.ptr_, nullptr)) {}
Obj& operator=(Obj&& o) noexcept {
if (this != &o) { reset(); ptr_ = std::exchange(o.ptr_, nullptr); }
return *this;
}
Obj(const Obj&) = delete;
Obj& operator=(const Obj&) = delete;
[[nodiscard]] lv_obj_t* raw() const { return ptr_; }
void reset() { if (ptr_) { lv_obj_del(ptr_); ptr_ = nullptr; } }
// 通用链式
Obj& size(const lv_coord_t w, const lv_coord_t h) { lv_obj_set_size(ptr_, w, h); return *this; }
Obj& pos(const lv_coord_t x, const lv_coord_t y) { lv_obj_set_pos(ptr_, x, y); return *this; }
Obj& center() { lv_obj_center(ptr_); return *this; }
Obj& align(const lv_align_t align, const lv_obj_t* base = nullptr, const lv_coord_t x = 0, const lv_coord_t y = 0) {
if (base) { // 如果有 base
lv_obj_align_to(ptr_, base, align, x, y);
}else {
lv_obj_align(ptr_, align, x, y);
}
return *this;
}
Obj& add_flag(const lv_obj_flag_t f) { lv_obj_add_flag(ptr_, f); return *this; }
Obj& clear_flag(const lv_obj_flag_t f) { lv_obj_clear_flag(ptr_, f); return *this; }
Obj& add_style(lv_style_t* style, const lv_style_selector_t sel = LV_PART_MAIN) {
lv_obj_add_style(ptr_, style, sel); return *this;
}
// 事件转发
using EventCb = std::function<void(lv_event_t*)>;
Obj& on(const lv_event_code_t code, EventCb cb) {
auto* ctx = new EventCb(std::move(cb));
lv_obj_add_event_cb(ptr_, event_trampoline, code, ctx);
return *this;
}
protected: // 改为 protected 以允许派生类访问
lv_obj_t* ptr_ = nullptr;
private:
static void event_trampoline(lv_event_t* e) {
const auto* ctx = static_cast<EventCb*>(lv_event_get_user_data(e));
(*ctx)(e);
}
};
// 样式构造器
struct Style {
lv_style_t s{};
Style() { lv_style_init(&s); }
~Style() { lv_style_reset(&s); }
Style& bg(lv_color_t c, lv_opa_t o = LV_OPA_COVER) {
lv_style_set_bg_color(&s, c);
lv_style_set_bg_opa(&s, o); return *this;
}
Style& radius(const lv_coord_t r) { lv_style_set_radius(&s, r); return *this; }
Style& pad_all(const lv_coord_t v) { lv_style_set_pad_all(&s, v); return *this; }
Style& border(const lv_color_t c, const lv_coord_t w = 1) {
lv_style_set_border_color(&s, c);
lv_style_set_border_width(&s, w); return *this;
}
Style& text_color(const lv_color_t c) { lv_style_set_text_color(&s, c); return *this; }
Style& shadow(const lv_coord_t w, const lv_color_t c) {
lv_style_set_shadow_width(&s, w);
lv_style_set_shadow_color(&s, c); return *this;
}
explicit operator lv_style_t*() { return &s; }
};
// 动画助手
template<typename T>
struct Anim {
lv_anim_t a{};
Anim(T* var, const int32_t start, const int32_t end, const uint32_t time = 300, const lv_anim_path_cb_t path = lv_anim_path_ease_out) {
lv_anim_init(&a);
lv_anim_set_var(&a, var);
lv_anim_set_values(&a, start, end);
lv_anim_set_time(&a, time);
lv_anim_set_path_cb(&a, path);
}
Anim& exec_cb(const lv_anim_exec_xcb_t cb) { lv_anim_set_exec_cb(&a, cb); return *this; }
Anim& ready_cb(const lv_anim_ready_cb_t cb) { lv_anim_set_ready_cb(&a, cb); return *this; }
void start() const { lv_anim_start(&a); }
};
// 具体控件
class Screen : public Obj {
public:
Screen() : Obj(lv_obj_create(nullptr)) {}
};
class Label : public Obj {
public:
explicit Label(const Obj& parent) : Obj(lv_label_create(parent.raw())) {}
Label& text(const char* txt) { lv_label_set_text(raw(), txt); return *this; }
};
class Button : public Obj {
public:
explicit Button(const Obj& parent) : Obj(lv_btn_create(parent.raw())) {}
};
class Arc : public Obj {
public:
explicit Arc(const Obj& parent) : Obj(lv_arc_create(parent.raw())) {}
Arc& value(const int16_t v) { lv_arc_set_value(raw(), v); return *this; }
Arc& range(const int16_t min, const int16_t max) { lv_arc_set_range(raw(), min, max); return *this; }
};
class Slider : public Obj {
public:
explicit Slider(const Obj& parent) : Obj(lv_slider_create(parent.raw())) {}
Slider& value(const int32_t v) { lv_slider_set_value(raw(), v, LV_ANIM_OFF); return *this; }
Slider& range(const int32_t min, const int32_t max) { lv_slider_set_range(raw(), min, max); return *this; }
};
class Switch : public Obj {
public:
explicit Switch(const Obj& parent) : Obj(lv_switch_create(parent.raw())) {}
};
class Roller : public Obj {
public:
explicit Roller(const Obj& parent) : Obj(lv_roller_create(parent.raw())) {}
Roller& options(const char* opts, const lv_roller_mode_t m = LV_ROLLER_MODE_NORMAL) {
lv_roller_set_options(raw(), opts, m); return *this;
}
};
class Dropdown : public Obj {
public:
explicit Dropdown(const Obj& parent) : Obj(lv_dropdown_create(parent.raw())) {}
Dropdown& options(const char* opts) { lv_dropdown_set_options(raw(), opts); return *this; }
};
class List : public Obj {
public:
explicit List(const Obj& parent) : Obj(lv_list_create(parent.raw())) {}
lv_obj_t* add_btn(const void* img, const char* txt) const { return lv_list_add_btn(raw(), img, txt); }
};
class TextArea : public Obj {
public:
explicit TextArea(const Obj& parent) : Obj(lv_textarea_create(parent.raw())) {}
TextArea& text(const char* txt) { lv_textarea_set_text(raw(), txt); return *this; }
TextArea& placeholder(const char* txt) { lv_textarea_set_placeholder_text(raw(), txt); return *this; }
};
class Image : public Obj {
public:
explicit Image(const Obj& parent) : Obj(lv_img_create(parent.raw())) {}
// 读 bin → PSRAM → 一次性 dsc → 显示
Image& bin(const char* fileName, const uint32_t w, const uint32_t h)
{
// 读文件
const std::string data = SDFileManager::getInstance()->readFileSync(ToolsClass::makeFullPath(fileName));
if (data.empty()) {
ESP_LOGE("Image", "bin read fail: %s", fileName);
return *this;
}
size_t sz = data.size();
if (sz != w * h * 2) { // RGB565 检查
ESP_LOGW("Image", "size mismatch, expect %lu, got %zu", w * h * 2, sz);
}
// 拷到 PSRAMLVGL 长期持有)
void* buf = heap_caps_malloc(sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!buf) {
ESP_LOGE("Image", "PSRAM malloc failed");
return *this;
}
memcpy(buf, data.data(), sz);
// 构造静态描述符(生命周期跟随 Image 对象)
dsc_ = std::unique_ptr<lv_img_dsc_t, DscDeleter>(new lv_img_dsc_t{});
dsc_->data = static_cast<const uint8_t*>(buf);
dsc_->header.cf = LV_IMG_CF_TRUE_COLOR;
dsc_->header.w = w;
dsc_->header.h = h;
dsc_->data_size = sz;
// 交给 LVGL
lv_img_set_src(raw(), dsc_.get());
return *this;
}
private:
// 用 unique_ptr 托管,Image 析构时自动释放 PSRAM 与 dsc
struct DscDeleter {
void operator()(const lv_img_dsc_t* p) const {
if (p && p->data) heap_caps_free(const_cast<uint8_t*>(p->data));
delete p;
}
};
std::unique_ptr<lv_img_dsc_t, DscDeleter> dsc_;
};
class IconButton : public Button {
public:
explicit IconButton(const Obj& parent) : Button(parent) {}
IconButton& icon(const char* fileName, const uint32_t w, const uint32_t h)
{
// 图标作为独立子 Image 创建
img_ = std::make_unique<Image>(*this);
img_->bin(fileName, w, h);
lv_obj_center(img_->raw());
return *this;
}
private:
std::unique_ptr<Image> img_; // 跟随 IconButton 生命周期
};
// 消息框 MsgBox
class MsgBox : public Obj {
public:
// 静态创建:模态、居中、自动大小
static MsgBox create(const char* title,
const char* txt,
const char* btns[] = nullptr) // nullptr -> 默认 "OK"
{
lv_obj_t* mbox = lv_msgbox_create(nullptr, title, txt, btns, true);
lv_obj_center(mbox);
return MsgBox{mbox};
}
// 关闭并删除自身
void close() { lv_msgbox_close(raw()); ptr_ = nullptr; }
private:
explicit MsgBox(lv_obj_t* p) : Obj(p) {}
};
// 进度条 Bar
class Bar : public Obj {
public:
explicit Bar(const Obj& parent) : Obj(lv_bar_create(parent.raw())) {}
Bar& range(const int32_t min, const int32_t max) { lv_bar_set_range(raw(), min, max); return *this; }
Bar& value(const int32_t v, const lv_anim_enable_t anim = LV_ANIM_OFF) {
lv_bar_set_value(raw(), v, anim); return *this;
}
// 双向模式
Bar& start_value(const int32_t v, const lv_anim_enable_t anim = LV_ANIM_OFF) {
lv_bar_set_start_value(raw(), v, anim); return *this;
}
Bar& anim_time(const uint32_t ms) { lv_obj_set_style_anim_time(raw(), ms, LV_PART_MAIN); return *this; }
};
// 折线 Line
class Line : public Obj {
public:
explicit Line(const Obj& parent) : Obj(lv_line_create(parent.raw())) {}
/* 直接传 std::vector<lv_point_t> 更安全 */
Line& points(const std::vector<lv_point_t>& v) {
lv_line_set_points(raw(), v.data(), v.size()); return *this;
}
Line& y_invert(const bool en) { lv_line_set_y_invert(raw(), en); return *this; }
};
// 颜色选择器 ColorPicker
class ColorPicker : public Obj {
public:
explicit ColorPicker(const Obj& parent) : Obj(lv_colorwheel_create(parent.raw(), true)) {}
// 获取/设置 HSV
[[nodiscard]] lv_color_t color() const { return lv_colorwheel_get_rgb(raw()); }
ColorPicker& color(const lv_color_t c) { lv_colorwheel_set_rgb(raw(), c); return *this; }
// 事件回调:LV_EVENT_VALUE_CHANGED 即可拿到新颜色
};
// Timer 封装(C++ lambda 版)
class Timer {
public:
using Cb = std::function<void()>;
// 构造即创建,默认循环模式
explicit Timer(Cb cb, const uint32_t period_ms, const bool once = false)
: cb_(new Cb(std::move(cb)))
{
t_ = lv_timer_create(timer_trampoline, period_ms, cb_.get());
if (once) lv_timer_set_repeat_count(t_, 1);
}
~Timer() { // RAII:自动反注册、释放
if (t_) lv_timer_del(t_);
}
// 手动停止/重启
void pause() const { lv_timer_pause(t_); }
void resume() const { lv_timer_resume(t_); }
void reset() const { lv_timer_reset(t_); }
// 修改周期
Timer& period(const uint32_t ms) { lv_timer_set_period(t_, ms); return *this; }
private:
lv_timer_t* t_ = nullptr;
std::unique_ptr<Cb> cb_; // 托管 lambda 生命周期
static void timer_trampoline(lv_timer_t* tm) {
const auto* f = static_cast<Cb*>(tm->user_data);
(*f)();
}
};
// Toast 消息泡泡
class Toast {
public:
enum class Type : uint8_t { INFO, WARN, ERROR };
// 一键弹出,默认底部居中 2 s
static void show(const char* txt,
const Type tp = Type::INFO,
const uint32_t duration_ms = 2000,
const lv_align_t align = LV_ALIGN_BOTTOM_MID,
const lv_coord_t offset_y = -60)
{
// 确保在 LVGL 线程里执行
if (!lv_is_initialized()) return;
lv_async_call(
[](void* p) {
auto* ctx = static_cast<Context*>(p);
create_internal(ctx);
delete ctx;
},
new Context{txt, tp, duration_ms, align, offset_y});
}
private:
struct Context {
const char* txt;
Type tp;
uint32_t dur;
lv_align_t align;
lv_coord_t off_y;
};
static void create_internal(const Context* ctx)
{
// 根容器(透明全屏)
lv_obj_t* parent = lv_layer_top(); // 顶层 overlay
lv_obj_t* toast = lv_obj_create(parent);
lv_obj_set_size(toast, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_scrollbar_mode(toast, LV_SCROLLBAR_MODE_OFF);
lv_obj_clear_flag(toast, LV_OBJ_FLAG_SCROLLABLE);
// 去掉边框 / 背景 / 阴影
lv_obj_set_style_border_width(toast, 0, 0);
lv_obj_set_style_bg_opa(toast, LV_OPA_TRANSP, 0);
lv_obj_set_style_shadow_width(toast, 0, 0);
// 消息标签
lv_obj_t* label = lv_label_create(toast);
lv_label_set_text(label, ctx->txt);
lv_obj_set_style_pad_hor(label, 12, 0);
lv_obj_set_style_pad_ver(label, 8, 0);
lv_obj_set_style_radius(label, 8, 0);
lv_obj_set_style_text_color(label, lv_color_white(), 0);
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
// 根据类型配色
switch (ctx->tp) {
case Type::INFO:
lv_obj_set_style_bg_color(label, lv_color_hex(0x2196F3), 0);
break;
case Type::WARN:
lv_obj_set_style_bg_color(label, lv_color_hex(0xFF9800), 0);
break;
case Type::ERROR:
lv_obj_set_style_bg_color(label, lv_color_hex(0xF44336), 0);
break;
}
lv_obj_set_style_bg_opa(label, LV_OPA_COVER, 0);
// 定位
lv_obj_align(toast, ctx->align, 0, ctx->off_y);
// 进场动画:向上淡入
lv_obj_set_style_translate_y(toast, 30, 0);
lv_obj_set_style_opa(toast, LV_OPA_0, 0);
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, toast);
lv_anim_set_time(&a, 250);
lv_anim_set_values(&a, LV_OPA_0, LV_OPA_COVER);
lv_anim_set_exec_cb(&a, [](void* obj, int32_t v) {
lv_obj_set_style_opa(static_cast<lv_obj_t *>(obj), v, 0);
lv_obj_set_style_translate_y(static_cast<lv_obj_t *>(obj), 30 - (v * 30) / 255, 0);
});
lv_anim_start(&a);
// 定时器:到达时间后淡出并删除
lv_timer_create(
[](lv_timer_t* t) {
auto* toast = static_cast<lv_obj_t *>(t->user_data);
/* 淡出 */
lv_anim_t a2;
lv_anim_init(&a2);
lv_anim_set_var(&a2, toast);
lv_anim_set_time(&a2, 200);
lv_anim_set_values(&a2, LV_OPA_COVER, LV_OPA_0);
lv_anim_set_exec_cb(&a2, [](void* obj, int32_t v) {
lv_obj_set_style_opa(static_cast<lv_obj_t *>(obj), v, 0);
});
lv_anim_set_ready_cb(&a2, [](lv_anim_t* a) {
lv_obj_del(static_cast<lv_obj_t *>(a->var));
});
lv_anim_start(&a2);
lv_timer_del(t); // 自毁
},
ctx->dur, toast);
}
};
// PhoneStyle-Battery
class Battery : public Obj {
public:
explicit Battery(const Obj& parent) : Obj(lv_obj_create(parent.raw())) {
// 主容器:圆角 + 边框 + 阴影
lv_obj_set_style_radius(raw(), 6, 0);
lv_obj_set_style_bg_color(raw(), lv_color_hex(0x9E9E9E), 0);
lv_obj_set_style_bg_opa(raw(), LV_OPA_COVER, 0);
lv_obj_set_style_border_width(raw(), 2, 0);
lv_obj_set_style_border_color(raw(), lv_color_hex(0x616161), 0);
lv_obj_set_style_shadow_width(raw(), 8, 0);
lv_obj_set_style_shadow_ofs_y(raw(), 4, 0);
lv_obj_set_style_shadow_color(raw(), lv_color_hex(0x9E9E9E), 0);
lv_obj_set_style_pad_all(raw(), 2, 0); // 内容区留白 2 px
lv_obj_set_size(raw(), LV_SIZE_CONTENT, LV_SIZE_CONTENT);
// 电量柱(动态宽/高)
fill_ = lv_obj_create(raw());
lv_obj_set_style_radius(fill_, 5, 0);
lv_obj_set_style_bg_opa(fill_, LV_OPA_COVER, 0);
lv_obj_set_size(fill_, 0, LV_PCT(100)); // 初始 0 %
applyGradient(); // 渐变刷一次
// 百分比文字(默认显示)
label_ = lv_label_create(raw());
lv_label_set_text(label_, "0%");
lv_obj_set_style_text_color(label_, lv_color_white(), 0);
lv_obj_set_style_text_font(label_, &lv_font_montserrat_14, 0);
lv_obj_center(label_);
}
// 设置整体尺寸(w×h)→ 自动计算内容区
Battery& size(const lv_coord_t w, const lv_coord_t h) {
lv_obj_set_size(raw(), w, h);
return *this;
}
// 显示/隐藏百分比文字
Battery& percent(const bool show) {
if (show)
lv_obj_clear_flag(label_, LV_OBJ_FLAG_HIDDEN);
else
lv_obj_add_flag(label_, LV_OBJ_FLAG_HIDDEN);
return *this;
}
// 注册电量回调 0~100,并启动 5 s 定时器
Battery& onRead(std::function<uint8_t()> provider) {
provider_ = std::move(provider);
update(); // 立即一次
if (!timer_) {
timer_ = lv_timer_create([](lv_timer_t* t){
auto* self = static_cast<Battery *>(t->user_data);
self->update();
}, 5000, this);
}
return *this;
}
// 手动刷新一次(公开)
Battery& update() {
if (!provider_) return *this;
pct_ = provider_();
pct_ = pct_ > 100 ? 100 : pct_;
// 更新电量条长度(保留 2 px 内边距)
const lv_coord_t content = lv_obj_get_content_width(raw()); // 去掉边框+pad
const auto fill_w = static_cast<lv_coord_t>((static_cast<int>(content) * pct_) / 100);
lv_obj_set_width(fill_, fill_w);
// 变色 + 渐变重刷
if (pct_ > 60) mainColor_ = lv_color_hex(0x4CAF50); // 绿
else if (pct_ > 20) mainColor_ = lv_color_hex(0xFFEB3B); // 黄
else mainColor_ = lv_color_hex(0xF44336); // 红
applyGradient();
// 文字
lv_label_set_text_fmt(label_, "%" LV_PRId32 "%%", static_cast<int32_t>(pct_));
return *this;
}
// 链式对齐
Battery& align(const lv_align_t align_type, const lv_coord_t x_ofs = 0, const lv_coord_t y_ofs = 0) {
lv_obj_align(raw(), align_type, x_ofs, y_ofs);
return *this;
}
private:
void applyGradient() const {
// 构造一次性渐变描述符(浅→主→深)
static lv_grad_dsc_t grad;
grad.dir = LV_GRAD_DIR_HOR;
grad.stops_count = 3;
grad.stops[0].color = lv_color_lighten(mainColor_, LV_OPA_30);
grad.stops[0].frac = 0;
grad.stops[1].color = mainColor_;
grad.stops[1].frac = 128;
grad.stops[2].color = lv_color_darken(mainColor_, LV_OPA_30);
grad.stops[2].frac = 255;
lv_obj_set_style_bg_grad(fill_, &grad, 0);
}
uint8_t pct_ = 50;
lv_color_t mainColor_ = lv_color_hex(0x4CAF50);
lv_obj_t* fill_;
lv_obj_t* label_;
lv_timer_t* timer_ = nullptr;
std::function<uint8_t()> provider_;
};
// DateTime 日期时间
class DateTime : public Obj {
public:
explicit DateTime(const Obj& parent) : Obj(lv_label_create(parent.raw())) {
lv_label_set_text(raw(), "--/--/-- --:--:--");
}
// 设置格式串,LVGL 语法,如 " %m/%d/%y %H:%M:%S"
DateTime& format(const char* fmt) {
fmt_ = fmt; refresh(); return *this;
}
// 注册数据源回调:外部把格式化好的字符串写进 buf
DateTime& onRead(std::function<void(char* buf, size_t)> cb) {
readCb_ = std::move(cb);
refresh();
// 1 s 定时刷新
timer_ = lv_timer_create([](lv_timer_t* t){
auto* self = static_cast<DateTime *>(t->user_data);
self->refresh();
}, 1000, this);
return *this;
}
// 立即刷新一次
DateTime& refresh() {
if (!readCb_) return *this;
readCb_(buf_, sizeof(buf_));
lv_label_set_text(raw(), buf_);
return *this;
}
private:
lv_timer_t* timer_ = nullptr;
std::function<void(char*, size_t)> readCb_;
std::string fmt_ = "%Y/%m/%d %H:%M:%S";
char buf_[64] = {0};
};
// GIF 显示控件
class Gif final : public Obj {
public:
explicit Gif(const Obj& parent) : Obj(lv_gif_create(parent.raw())) {}
bool getGifWH(const uint8_t* raw, uint32_t& w, uint32_t& h)
{
if (!raw || memcmp(raw, "GIF", 3) != 0) {
ESP_LOGE("LVGLRender", "不是合法 GIF 文件头");
return false;
}
// GIF87a/89a 宽高偏移 6~9 字节,小端
w = raw[6] | (raw[7] << 8);
h = raw[8] | (raw[9] << 8);
ESP_LOGI("LVGLRender", "GIF 尺寸: %lu x %lu", w, h);
return true;
}
// 从 SD 卡加载 GIF 文件(完整路径)
Gif& src(const char* full_path) {
// 读整个文件到 PSRAM
std::string content = SDFileManager::getInstance()->readFileSync(ToolsClass::makeFullPath(full_path));
if (content.empty()) {
ESP_LOGE("Gif", "read %s fail", full_path);
return *this;
}
// 解析宽高
uint32_t w = 0, h = 0;
if (!getGifWH(reinterpret_cast<const uint8_t*>(content.data()), w, h)) {
ESP_LOGE("Gif", "bad GIF header");
return *this;
}
// 把数据拷到 PSRAMLVGL 长期持有)
void* buf = heap_caps_malloc(content.size(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!buf) {
ESP_LOGE("Gif", "PSRAM malloc fail");
return *this;
}
memcpy(buf, content.data(), content.size());
// 构造一次性描述符(跟随 Gif 对象生命周期)
dsc_ = std::unique_ptr<lv_img_dsc_t, DscDeleter>(new lv_img_dsc_t{});
dsc_->header.cf = LV_IMG_CF_RAW_CHROMA_KEYED;
dsc_->header.always_zero = 0;
dsc_->header.reserved = 0;
dsc_->header.w = static_cast<lv_coord_t>(w);
dsc_->header.h = static_cast<lv_coord_t>(h);
dsc_->data_size = content.size();
dsc_->data = static_cast<const uint8_t*>(buf);
// 交给 LVGL
lv_gif_set_src(raw(), dsc_.get());
return *this;
}
// 控制播放/暂停
Gif& play() { lv_gif_restart(raw()); return *this; }
Gif& pause() { /*lv_gif_pause(raw());*/ return *this; }
private:
// 描述符 + PSRAM 一并释放
struct DscDeleter {
void operator()(lv_img_dsc_t* p) const {
if (p && p->data) heap_caps_free(const_cast<uint8_t*>(p->data));
delete p;
}
};
std::unique_ptr<lv_img_dsc_t, DscDeleter> dsc_;
};
// 子应用抽象
class BaseApp : public Obj {
public:
explicit BaseApp(const Obj& parent) : Obj(lv_obj_create(parent.raw())), btn_exit_(parent) {
lv_obj_set_size(raw(), 360, 360);
// 退出按钮
btn_exit_.size(40, 40)
.align(LV_ALIGN_CENTER, nullptr, 0, -130) // 居中
.on(LV_EVENT_CLICKED, [this](lv_event_t *) { this->exit(); });
label_.align(LV_ALIGN_CENTER, nullptr, 0, 0);
label_.text(LV_SYMBOL_CLOSE);
}
// 子类重写:界面内容
virtual void onShow() {} // 进入时调用
virtual void onHide() {} // 退出时调用
// 主动退出
void exit() {
onHide();
// 禁止自己再被点击
lv_obj_clear_flag(raw(), LV_OBJ_FLAG_CLICKABLE);
// 异步自毁:让 LVGL 在下一轮循环再删我们
lv_async_call([](void* self) {
lv_obj_del(static_cast<lv_obj_t*>(self));
}, raw());
// 通知工厂回到主页 → 由工厂/菜单负责
if (exit_cb_) exit_cb_();
}
// 注册退出通知回调(由 AppFactory 设置)
void onExit(std::function<void()> cb) { exit_cb_ = std::move(cb); }
protected:
Button btn_exit_{*this}; // 退出按钮
Label label_{btn_exit_}; // 退出图标
private:
std::function<void()> exit_cb_;
};
// 子应用工厂
class AppFactory {
public:
using Creator = std::function<std::unique_ptr<BaseApp>(Obj& parent)>;
// 注册新应用:名字 + lambda 创建器
static void registerApp(const char* name, Creator&& creator) {
registry()[name] = std::move(creator);
}
// 创建实例:parent → 传入当前屏幕
static std::unique_ptr<BaseApp> create(const char* name, Obj& parent) {
auto it = registry().find(name);
if (it == registry().end()) return nullptr;
return it->second(parent);
}
private:
static std::unordered_map<std::string, Creator>& registry() {
static std::unordered_map<std::string, Creator> inst;
return inst;
}
};
// 应用菜单(网格)
class AppMenu : public Obj {
public:
explicit AppMenu(const Obj& parent) : Obj(lv_obj_create(parent.raw())) {
lv_obj_set_size(raw(), 360, 360);
lv_obj_set_style_bg_opa(raw(), LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(raw(), 0, 0);
lv_obj_clear_flag(raw(), LV_OBJ_FLAG_SCROLLABLE); // 圆形屏建议不要滚动
lv_obj_set_style_pad_top(raw(), 60, 0); // ← 新增
lv_obj_set_style_pad_bottom(raw(),60, 0); // ← 新增
lv_obj_set_style_pad_left(raw(), 20, 0);
lv_obj_set_style_pad_right(raw(), 20, 0);
// 横向 2 列,居中排列
lv_obj_set_flex_flow(raw(), LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_flex_align(raw(), LV_FLEX_ALIGN_SPACE_EVENLY,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
}
// 添加一个应用图标:图标 bin + 名字
AppMenu& addItem(const char* name, // 应用名
lv_coord_t w = 140, // 按钮宽
lv_coord_t h = 50) // 按钮高
{
// 每项:一个文字按钮
auto* btn = lv_btn_create(raw());
lv_obj_set_size(btn, w, h);
lv_obj_set_style_radius(btn, 8, 0);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text(label, name);
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
lv_obj_center(label);
// 点击 -> 回调
lv_obj_add_event_cb(btn, [](lv_event_t* e){
const auto* self = static_cast<AppMenu *>(lv_event_get_user_data(e));
if (self && self->click_cb_) {
const char* txt = lv_label_get_text(
lv_obj_get_child(lv_event_get_target(e), 0));
self->click_cb_(txt);
}
}, LV_EVENT_CLICKED, this);
return *this;
}
// 注册点击回调:返回应用名
AppMenu& onClick(std::function<void(const char*)> cb) {
click_cb_ = std::move(cb);
return *this;
}
private:
std::function<void(const char*)> click_cb_;
};
// 主页
class HomePage : public Obj {
public:
explicit HomePage(const Obj& parent) : Obj(lv_obj_create(parent.raw())) {
lv_obj_set_size(raw(), 360, 360);
lv_obj_set_style_radius(raw(), LV_RADIUS_CIRCLE, 0); // 圆形裁切
lv_obj_set_style_clip_corner(raw(), true, 0);
lv_obj_set_style_border_width(raw(), 0, 0);
// 彻底禁止滚动
lv_obj_clear_flag(raw(), LV_OBJ_FLAG_SCROLLABLE); // 禁止滚动
// lv_obj_set_scroll_dir(raw(), LV_DIR_NONE);
// 背景图片(默认空,可热切换)
bg_img_ = std::make_unique<Image>(*this);
// 顶栏电池
battery_ = std::make_unique<Battery>(*this);
battery_->size(60, 30)
.percent(true)
.align(LV_ALIGN_CENTER, -10, -150);
// 底栏时间
datetime_ = std::make_unique<DateTime>(*this);
datetime_->format("%m/%d %a %H:%M")
.align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -10);
// 长按识别:按住 500 ms 即进入应用菜单
lv_obj_add_flag(raw(), LV_OBJ_FLAG_CLICKABLE); // 必须置位,否则收不到输入事件
on(LV_EVENT_LONG_PRESSED, [this](lv_event_t*) {
if (menu_open_cb_) menu_open_cb_();
});
}
// 热切换背景
HomePage& bg(const char* bin_path, const uint32_t w, const uint32_t h) {
bg_img_->bin(bin_path, w, h);
bg_img_->center();
return *this;
}
// 注册电量回调
HomePage& onBattery(std::function<uint8_t()> cb) {
battery_->onRead(std::move(cb));
return *this;
}
// 注册时间回调
HomePage& onDateTime(std::function<void(char*, size_t)> cb) {
datetime_->onRead(std::move(cb));
return *this;
}
// 注册右滑 → 打开菜单的回调
HomePage& onOpenMenu(std::function<void()> cb) {
menu_open_cb_ = std::move(cb);
return *this;
}
private:
std::unique_ptr<Image> bg_img_;
std::unique_ptr<Battery> battery_;
std::unique_ptr<DateTime> datetime_;
std::function<void()> menu_open_cb_;
};
} // namespace lvgl
@@ -92,6 +92,39 @@ std::string SDFileManager::readFileSync(const char* path) {
return content; return content;
} }
std::string SDFileManager::readFileSync(const std::string& path) {
std::lock_guard<std::mutex> lock(file_operation_mutex);
if (!is_initialized) {
ESP_LOGE("SDFileManager", "SD card not initialized");
return "";
}
FILE* file = fopen(path.c_str(), "r");
if (file == nullptr) {
ESP_LOGE("SDFileManager", "Failed to open file: %s", path.c_str());
return "";
}
// 获取文件大小
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
// 读取文件内容
std::string content;
content.resize(size);
size_t bytes_read = fread(&content[0], 1, size, file);
fclose(file);
if (bytes_read != static_cast<size_t>(size)) {
ESP_LOGE("SDFileManager", "Failed to read entire file: %s", path.c_str());
return "";
}
return content;
}
std::vector<std::string> SDFileManager::listFilesSync(const char* directory, const char* extension) { std::vector<std::string> SDFileManager::listFilesSync(const char* directory, const char* extension) {
std::lock_guard<std::mutex> lock(file_operation_mutex); std::lock_guard<std::mutex> lock(file_operation_mutex);
@@ -44,6 +44,13 @@ public:
*/ */
std::string readFileSync(const char* path); std::string readFileSync(const char* path);
/**
* 同步读取文件(std::string)
* @param path 文件路径
* @return 文件内容
*/
std::string readFileSync(const std::string& path);
/** /**
* 同步列出目录下的文件 * 同步列出目录下的文件
* @param directory 目录路径 * @param directory 目录路径
+37
View File
@@ -12,9 +12,46 @@
#include <iomanip> #include <iomanip>
#include <algorithm> #include <algorithm>
#include "BAT_Driver.h"
#include "PWR_Key.h"
static const char *TAG = "ToolsClass"; static const char *TAG = "ToolsClass";
ToolsClass* ToolsClass::instance;
std::mutex ToolsClass::instance_mutex;
ToolsClass *ToolsClass::getInstance() {
std::lock_guard<std::mutex> lock(instance_mutex);
if (!instance) {
instance = new ToolsClass();
}
return instance;
}
ToolsClass::ToolsClass() {
ESP_LOGI(TAG, "ToolsClass init");
PWR_Init();
BAT_Init(); // 电池驱动初始化
}
float ToolsClass::getBatteryVolts() {
return BAT_Get_Volts();
}
int32_t ToolsClass::getBatteryPer() {
return BAT_Get_Percent();
}
// 静态:拼出完整路径
std::string ToolsClass::makeFullPath(const std::string& filename)
{
return "/sdcard/" + filename;
}
std::string ToolsClass::getChipSerialNumber() { std::string ToolsClass::getChipSerialNumber() {
// 获取芯片序列号(17 字节长的 "Chip ID" (乐鑫官方唯一序列号)) // 获取芯片序列号(17 字节长的 "Chip ID" (乐鑫官方唯一序列号))
std::array<uint8_t, 17> id{}; std::array<uint8_t, 17> id{};
+32
View File
@@ -3,12 +3,25 @@
// //
#pragma once #pragma once
#include <string> #include <string>
#include <mutex>
/** /**
* 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装 * 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装
* *
* *
*/ */
class ToolsClass { class ToolsClass {
public:
static ToolsClass* getInstance();
private:
ToolsClass();
~ToolsClass() = default;
private:
static ToolsClass* instance;
static std::mutex instance_mutex;
public: public:
/** /**
* 获取当前时间 * 获取当前时间
@@ -37,6 +50,25 @@ public:
*/ */
static std::string GenerateSN(const std::string& mac, const std::string& chipID); static std::string GenerateSN(const std::string& mac, const std::string& chipID);
/**
* 获取电池电压
* @return 电池电压
*/
float getBatteryVolts();
/**
* 获取电池电量
* @return 电池电量百分比(0 ~ 100)
*/
int32_t getBatteryPer();
/**
* 拼接文件路径(for sd card)
* @param filename 文件名
* @return 拼接后的文件路径
*/
static std::string makeFullPath(const std::string& filename);
/** /**
* 获取设备版本 * 获取设备版本
* @return 设备版本 * @return 设备版本
+43
View File
@@ -114,4 +114,47 @@ float BAT_Get_Volts(void)
// printf("BAT voltage : %.2f V\r\n", BAT_analogVolts); // printf("BAT voltage : %.2f V\r\n", BAT_analogVolts);
} }
return BAT_analogVolts; return BAT_analogVolts;
}
/**
* @brief 滞回锁定百分比(±1 % 死区)
* @param pct:实时算出的 0-100
* @retval 对外发布的稳定百分比
*/
int32_t BAT_Percent_Lock(int32_t pct)
{
static int32_t out = 0; // 上次对外值
if (pct >= out + 1) out = pct; // 上升门槛
if (pct <= out - 1) out = pct; // 下降门槛
return out;
}
static float lpf = 0.0f; // 上一次滤波结果
static bool first = true; // 首次赋值标志
float BAT_Get_Volts_Filtered(void)
{
float raw = BAT_Get_Volts(); // 你的原始函数
if (first) { lpf = raw; first = false; } // 第一次直接赋值
lpf = lpf + 0.05f * (raw - lpf); // α=0.05 越小刚越稳
return lpf;
}
/**
* @brief 返回电池剩余电量百分比(0~100)
* @note 基于 200 k + 100 k 分压,ADC 满量程 1.1 V
* @retval 0 已放空(≤ 3.0 V
* 100 满电(≥ 4.2 V) 这里给到4.1V 以放置电池达不到100%的电量
* 中间线性
*/
int32_t BAT_Get_Percent(void)
{
const float volts = BAT_Get_Volts_Filtered(); // 3.0 ~ 4.2 V -> 0 ~ 100 做映射
// 限幅
if (volts <= 3.0f) return 0;
if (volts >= 4.1f) return 100;
// 滞回比较器 + 线性映射
return BAT_Percent_Lock((int32_t)((volts - 3.0f) / (4.2f - 3.0f) * 100.0f + 0.5f)); // +0.5 四舍五入
} }
+2
View File
@@ -24,6 +24,8 @@ extern float BAT_analogVolts;
void BAT_Init(void); void BAT_Init(void);
float BAT_Get_Volts(void); float BAT_Get_Volts(void);
int32_t BAT_Get_Percent(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
+4
View File
@@ -38,6 +38,8 @@ idf_component_register(SRCS "Bionic_sphere.c"
"../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库 "../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库
"../Bionic_Core/ToolsClass/AudioOutput/AudioOutput.cpp" # 音频输出类库 "../Bionic_Core/ToolsClass/AudioOutput/AudioOutput.cpp" # 音频输出类库
"../Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.cpp" # LVGL渲染类库 "../Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.cpp" # LVGL渲染类库
"../Bionic_Core/ToolsClass/LVGL_Render/Lvpp/SiYuanHeiTiGoogleBan.c" # 中文字体库
"../Bionic_Core/ToolsClass/LVGL_Render/Lvpp/lvpp.cpp"
"../Bionic_Core/ToolsClass/SDFileManager/SDFileManager.cpp" # SD文件管理类库 "../Bionic_Core/ToolsClass/SDFileManager/SDFileManager.cpp" # SD文件管理类库
"../Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.cpp" # 语音识别类库 "../Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.cpp" # 语音识别类库
"../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库 "../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库
@@ -76,6 +78,7 @@ idf_component_register(SRCS "Bionic_sphere.c"
"../Bionic_Core/ToolsClass" "../Bionic_Core/ToolsClass"
"../Bionic_Core/ToolsClass/AudioOutput" "../Bionic_Core/ToolsClass/AudioOutput"
"../Bionic_Core/ToolsClass/LVGL_Render" "../Bionic_Core/ToolsClass/LVGL_Render"
"../Bionic_Core/ToolsClass/LVGL_Render/Lvpp"
"../Bionic_Core/ToolsClass/SDFileManager" "../Bionic_Core/ToolsClass/SDFileManager"
"../Bionic_Core/ToolsClass/SpeechRecognizer" "../Bionic_Core/ToolsClass/SpeechRecognizer"
"../Bionic_Core/ToolsClass/WifiConnectors" "../Bionic_Core/ToolsClass/WifiConnectors"
@@ -92,6 +95,7 @@ idf_component_register(SRCS "Bionic_sphere.c"
unity unity
esp_lcd esp_lcd
lvgl__lvgl lvgl__lvgl
lvgl
fatfs fatfs
pthread pthread
app_update app_update
+9 -6
View File
@@ -2775,7 +2775,7 @@ CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
CONFIG_LV_FONT_MONTSERRAT_12=y CONFIG_LV_FONT_MONTSERRAT_12=y
CONFIG_LV_FONT_MONTSERRAT_14=y CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_16=y CONFIG_LV_FONT_MONTSERRAT_16=y
# CONFIG_LV_FONT_MONTSERRAT_18 is not set CONFIG_LV_FONT_MONTSERRAT_18=y
# CONFIG_LV_FONT_MONTSERRAT_20 is not set # CONFIG_LV_FONT_MONTSERRAT_20 is not set
# CONFIG_LV_FONT_MONTSERRAT_22 is not set # CONFIG_LV_FONT_MONTSERRAT_22 is not set
# CONFIG_LV_FONT_MONTSERRAT_24 is not set # CONFIG_LV_FONT_MONTSERRAT_24 is not set
@@ -2794,7 +2794,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
# CONFIG_LV_FONT_MONTSERRAT_12_SUBPX is not set # CONFIG_LV_FONT_MONTSERRAT_12_SUBPX is not set
# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set # CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set
# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set # CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set
# CONFIG_LV_FONT_SIMSUN_16_CJK is not set CONFIG_LV_FONT_SIMSUN_16_CJK=y
# CONFIG_LV_FONT_UNSCII_8 is not set # CONFIG_LV_FONT_UNSCII_8 is not set
# CONFIG_LV_FONT_UNSCII_16 is not set # CONFIG_LV_FONT_UNSCII_16 is not set
# CONFIG_LV_FONT_CUSTOM is not set # CONFIG_LV_FONT_CUSTOM is not set
@@ -2802,7 +2802,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set
CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set
@@ -2823,7 +2823,7 @@ CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12_SUBPX is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12_SUBPX is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set
# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set # CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set
# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK=y
# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set # CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set
# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set # CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set
# CONFIG_LV_FONT_FMT_TXT_LARGE is not set # CONFIG_LV_FONT_FMT_TXT_LARGE is not set
@@ -2840,7 +2840,10 @@ CONFIG_LV_TXT_ENC_UTF8=y
CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_" CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_"
CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0
CONFIG_LV_TXT_COLOR_CMD="#" CONFIG_LV_TXT_COLOR_CMD="#"
# CONFIG_LV_USE_BIDI is not set CONFIG_LV_USE_BIDI=y
# CONFIG_LV_BIDI_DIR_LTR is not set
# CONFIG_LV_BIDI_DIR_RTL is not set
CONFIG_LV_BIDI_DIR_AUTO=y
# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set # CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set
# end of Text Settings # end of Text Settings
@@ -2920,7 +2923,7 @@ CONFIG_LV_USE_GRID=y
# CONFIG_LV_USE_FS_WIN32 is not set # CONFIG_LV_USE_FS_WIN32 is not set
# CONFIG_LV_USE_FS_FATFS is not set # CONFIG_LV_USE_FS_FATFS is not set
# CONFIG_LV_USE_FS_LITTLEFS is not set # CONFIG_LV_USE_FS_LITTLEFS is not set
# CONFIG_LV_USE_PNG is not set CONFIG_LV_USE_PNG=y
# CONFIG_LV_USE_BMP is not set # CONFIG_LV_USE_BMP is not set
# CONFIG_LV_USE_SJPG is not set # CONFIG_LV_USE_SJPG is not set
CONFIG_LV_USE_GIF=y CONFIG_LV_USE_GIF=y
+13 -8
View File
@@ -777,7 +777,8 @@ CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set # CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
CONFIG_COMPILER_HIDE_PATHS_MACROS=y CONFIG_COMPILER_HIDE_PATHS_MACROS=y
# CONFIG_COMPILER_CXX_EXCEPTIONS is not set CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
CONFIG_COMPILER_CXX_RTTI=y CONFIG_COMPILER_CXX_RTTI=y
CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set # CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
@@ -2774,7 +2775,7 @@ CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
CONFIG_LV_FONT_MONTSERRAT_12=y CONFIG_LV_FONT_MONTSERRAT_12=y
CONFIG_LV_FONT_MONTSERRAT_14=y CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_16=y CONFIG_LV_FONT_MONTSERRAT_16=y
# CONFIG_LV_FONT_MONTSERRAT_18 is not set CONFIG_LV_FONT_MONTSERRAT_18=y
# CONFIG_LV_FONT_MONTSERRAT_20 is not set # CONFIG_LV_FONT_MONTSERRAT_20 is not set
# CONFIG_LV_FONT_MONTSERRAT_22 is not set # CONFIG_LV_FONT_MONTSERRAT_22 is not set
# CONFIG_LV_FONT_MONTSERRAT_24 is not set # CONFIG_LV_FONT_MONTSERRAT_24 is not set
@@ -2793,7 +2794,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
# CONFIG_LV_FONT_MONTSERRAT_12_SUBPX is not set # CONFIG_LV_FONT_MONTSERRAT_12_SUBPX is not set
# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set # CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set
# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set # CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set
# CONFIG_LV_FONT_SIMSUN_16_CJK is not set CONFIG_LV_FONT_SIMSUN_16_CJK=y
# CONFIG_LV_FONT_UNSCII_8 is not set # CONFIG_LV_FONT_UNSCII_8 is not set
# CONFIG_LV_FONT_UNSCII_16 is not set # CONFIG_LV_FONT_UNSCII_16 is not set
# CONFIG_LV_FONT_CUSTOM is not set # CONFIG_LV_FONT_CUSTOM is not set
@@ -2801,7 +2802,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set
CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set
@@ -2822,7 +2823,7 @@ CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12_SUBPX is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12_SUBPX is not set
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set # CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set
# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set # CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set
# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK=y
# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set # CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set
# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set # CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set
# CONFIG_LV_FONT_FMT_TXT_LARGE is not set # CONFIG_LV_FONT_FMT_TXT_LARGE is not set
@@ -2839,7 +2840,10 @@ CONFIG_LV_TXT_ENC_UTF8=y
CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_" CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_"
CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0
CONFIG_LV_TXT_COLOR_CMD="#" CONFIG_LV_TXT_COLOR_CMD="#"
# CONFIG_LV_USE_BIDI is not set CONFIG_LV_USE_BIDI=y
# CONFIG_LV_BIDI_DIR_LTR is not set
# CONFIG_LV_BIDI_DIR_RTL is not set
CONFIG_LV_BIDI_DIR_AUTO=y
# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set # CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set
# end of Text Settings # end of Text Settings
@@ -2919,7 +2923,7 @@ CONFIG_LV_USE_GRID=y
# CONFIG_LV_USE_FS_WIN32 is not set # CONFIG_LV_USE_FS_WIN32 is not set
# CONFIG_LV_USE_FS_FATFS is not set # CONFIG_LV_USE_FS_FATFS is not set
# CONFIG_LV_USE_FS_LITTLEFS is not set # CONFIG_LV_USE_FS_LITTLEFS is not set
# CONFIG_LV_USE_PNG is not set CONFIG_LV_USE_PNG=y
# CONFIG_LV_USE_BMP is not set # CONFIG_LV_USE_BMP is not set
# CONFIG_LV_USE_SJPG is not set # CONFIG_LV_USE_SJPG is not set
CONFIG_LV_USE_GIF=y CONFIG_LV_USE_GIF=y
@@ -2995,7 +2999,8 @@ CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set # CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2
# CONFIG_CXX_EXCEPTIONS is not set CONFIG_CXX_EXCEPTIONS=y
CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
CONFIG_STACK_CHECK_NONE=y CONFIG_STACK_CHECK_NONE=y
# CONFIG_STACK_CHECK_NORM is not set # CONFIG_STACK_CHECK_NORM is not set
# CONFIG_STACK_CHECK_STRONG is not set # CONFIG_STACK_CHECK_STRONG is not set
+10
View File
@@ -258,3 +258,13 @@
- [x] 4. 重写OTA的逻辑,完成了Cpp的OTA封装,测试通过 - [x] 4. 重写OTA的逻辑,完成了Cpp的OTA封装,测试通过
#### Day18 2025.9.27
##### 主要目标:完成具体业务开发&各种优化
实际完成任务:
- [x] 1. 花了几天时间基于lvgl8.3封装了lvgl_cpp,写了一些基本需要的控件,支持链式调用
还存在一点点bug,不难fix
- [x] 2. 增加了中文字库,支持中文显示
- [x] 3. 修复和优化了一些地方