Merge pull request #16 from 78/ui

加入中文UI
This commit is contained in:
Xiaoxia 2024-11-18 20:41:27 +08:00 committed by GitHub
commit 6dcc64459f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 58931 additions and 237 deletions

View File

@ -4,7 +4,7 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "0.8.3")
set(PROJECT_VER "0.9.0")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(xiaozhi)

View File

@ -21,7 +21,7 @@
- Wi-Fi 配网
- 支持 BOOT 键唤醒和打断
- 离线语音唤醒(乐鑫方案)
- 流式语音对话WebSocket 协议)
- 流式语音对话WebSocket 或 UDP 协议)
- 支持国语、粤语、英语、日语、韩语 5 种语言识别SenseVoice 方案)
- 声纹识别(识别是谁在喊 AI 的名字,[3D Speaker 项目](https://github.com/modelscope/3D-Speaker)
- 使用大模型 TTS火山引擎与 CosyVoice 方案)

View File

@ -1,23 +1,26 @@
set(SOURCES "audio_codec.cc"
set(SOURCES "audio_codecs/audio_codec.cc"
"audio_codecs/no_audio_codec.cc"
"audio_codecs/box_audio_codec.cc"
"display.cc"
"display/display.cc"
"display/no_display.cc"
"display/st7789_display.cc"
"display/ssd1306_display.cc"
"board.cc"
"protocol.cc"
"protocols/protocol.cc"
"protocols/mqtt_protocol.cc"
"protocols/websocket_protocol.cc"
"system_info.cc"
"application.cc"
"button.cc"
"led.cc"
"ota.cc"
"settings.cc"
"main.cc"
)
set(INCLUDE_DIRS ".")
set(INCLUDE_DIRS "." "display" "audio_codecs" "protocols")
#
file(GLOB FONT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/fonts/*.c)
list(APPEND SOURCES ${FONT_SOURCES})
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/fonts)
#
file(GLOB BOARD_COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/common/*.cc)

View File

@ -2,8 +2,9 @@
#include "system_info.h"
#include "ml307_ssl_transport.h"
#include "audio_codec.h"
#include "protocols/mqtt_protocol.h"
#include "protocols/websocket_protocol.h"
#include "mqtt_protocol.h"
#include "websocket_protocol.h"
#include "font_awesome_symbols.h"
#include <cstring>
#include <esp_log.h>
@ -43,23 +44,29 @@ Application::~Application() {
}
void Application::CheckNewVersion() {
auto& board = Board::GetInstance();
auto display = board.GetDisplay();
// Check if there is a new firmware version available
ota_.SetPostData(Board::GetInstance().GetJson());
ota_.SetPostData(board.GetJson());
while (true) {
if (ota_.CheckVersion()) {
if (ota_.HasNewVersion()) {
// Wait for the chat state to be idle
while (chat_state_ != kChatStateIdle) {
vTaskDelay(100);
}
do {
vTaskDelay(pdMS_TO_TICKS(3000));
} while (GetChatState() != kChatStateIdle);
SetChatState(kChatStateUpgrading);
ota_.StartUpgrade([](int progress, size_t speed) {
display->SetIcon(FONT_AWESOME_DOWNLOAD);
display->SetStatus("新版本 " + ota_.GetFirmwareVersion());
board.GetAudioCodec()->EnableOutput(false);
ota_.StartUpgrade([display](int progress, size_t speed) {
char buffer[64];
snprintf(buffer, sizeof(buffer), "Upgrading...\n %d%% %zuKB/s", progress, speed / 1024);
auto display = Board::GetInstance().GetDisplay();
display->SetText(buffer);
snprintf(buffer, sizeof(buffer), "%d%% %zuKB/s", progress, speed / 1024);
display->SetStatus(buffer);
});
// If upgrade success, the device will reboot and never reach here
@ -67,6 +74,7 @@ void Application::CheckNewVersion() {
SetChatState(kChatStateIdle);
} else {
ota_.MarkCurrentVersionValid();
display->ShowNotification("版本 " + ota_.GetCurrentVersion());
}
return;
}
@ -79,7 +87,7 @@ void Application::CheckNewVersion() {
void Application::Alert(const std::string&& title, const std::string&& message) {
ESP_LOGW(TAG, "Alert: %s, %s", title.c_str(), message.c_str());
auto display = Board::GetInstance().GetDisplay();
display->ShowNotification(std::string(title + "\n" + message));
display->ShowNotification(message);
if (message == "PIN is not ready") {
PlayLocalFile(p3_err_pin_start, p3_err_pin_end - p3_err_pin_start);
@ -137,7 +145,6 @@ void Application::Start() {
/* Setup the display */
auto display = board.GetDisplay();
display->SetupUI();
/* Setup the audio codec */
auto codec = board.GetAudioCodec();
@ -230,9 +237,9 @@ void Application::Start() {
auto builtin_led = Board::GetInstance().GetBuiltinLed();
if (chat_state_ == kChatStateListening) {
if (speaking) {
builtin_led->SetRed(32);
builtin_led->SetRed(HIGH_BRIGHTNESS);
} else {
builtin_led->SetRed(8);
builtin_led->SetRed(LOW_BRIGHTNESS);
}
builtin_led->TurnOn();
}
@ -269,7 +276,7 @@ void Application::Start() {
#endif
// Initialize the protocol
display->SetText("Starting protocol...");
display->SetStatus("初始化协议");
#ifdef CONFIG_CONNECTION_TYPE_WEBSOCKET
protocol_ = new WebsocketProtocol();
#else
@ -294,7 +301,7 @@ void Application::Start() {
});
board.SetPowerSaveMode(true);
});
protocol_->OnIncomingJson([this](const cJSON* root) {
protocol_->OnIncomingJson([this, display](const cJSON* root) {
// Parse JSON data
auto type = cJSON_GetObjectItem(root, "type");
if (strcmp(type->valuestring, "tts") == 0) {
@ -315,28 +322,30 @@ void Application::Start() {
} else if (strcmp(state->valuestring, "sentence_start") == 0) {
auto text = cJSON_GetObjectItem(root, "text");
if (text != NULL) {
ESP_LOGI(TAG, ">> %s", text->valuestring);
ESP_LOGI(TAG, "<< %s", text->valuestring);
display->SetChatMessage("assistant", text->valuestring);
}
}
} else if (strcmp(type->valuestring, "stt") == 0) {
auto text = cJSON_GetObjectItem(root, "text");
if (text != NULL) {
ESP_LOGI(TAG, ">> %s", text->valuestring);
display->SetChatMessage("user", text->valuestring);
}
} else if (strcmp(type->valuestring, "llm") == 0) {
auto emotion = cJSON_GetObjectItem(root, "emotion");
if (emotion != NULL) {
ESP_LOGD(TAG, "EMOTION: %s", emotion->valuestring);
display->SetEmotion(emotion->valuestring);
}
}
});
// Blink the LED to indicate the device is running
display->SetStatus("待命");
builtin_led->SetGreen();
builtin_led->BlinkOnce();
SetChatState(kChatStateIdle);
display->UpdateDisplay();
}
void Application::Schedule(std::function<void()> callback) {
@ -394,7 +403,8 @@ void Application::SetChatState(ChatState state) {
case kChatStateUnknown:
case kChatStateIdle:
builtin_led->TurnOff();
display->SetText("I'm\nIdle.");
display->SetStatus("待命");
display->SetEmotion("neutral");
#ifdef CONFIG_USE_AFE_SR
audio_processor_.Stop();
#endif
@ -402,12 +412,13 @@ void Application::SetChatState(ChatState state) {
case kChatStateConnecting:
builtin_led->SetBlue();
builtin_led->TurnOn();
display->SetText("I'm\nConnecting...");
display->SetStatus("连接中...");
break;
case kChatStateListening:
builtin_led->SetRed();
builtin_led->TurnOn();
display->SetText("I'm\nListening...");
display->SetStatus("聆听中...");
display->SetEmotion("neutral");
#ifdef CONFIG_USE_AFE_SR
audio_processor_.Start();
#endif
@ -415,7 +426,7 @@ void Application::SetChatState(ChatState state) {
case kChatStateSpeaking:
builtin_led->SetGreen();
builtin_led->TurnOn();
display->SetText("I'm\nSpeaking...");
display->SetStatus("说话中...");
#ifdef CONFIG_USE_AFE_SR
audio_processor_.Stop();
#endif

View File

@ -23,6 +23,8 @@ public:
virtual ~AudioCodec();
virtual void SetOutputVolume(int volume);
virtual void EnableInput(bool enable);
virtual void EnableOutput(bool enable);
void Start();
void OnInputData(std::function<void(std::vector<int16_t>&& data)> callback);
@ -67,8 +69,6 @@ protected:
virtual int Read(int16_t* dest, int samples) = 0;
virtual int Write(const int16_t* data, int samples) = 0;
virtual void EnableInput(bool enable);
virtual void EnableOutput(bool enable);
};
#endif // _AUDIO_CODEC_H

View File

@ -22,8 +22,6 @@ private:
virtual int Read(int16_t* dest, int samples) override;
virtual int Write(const int16_t* data, int samples) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
public:
BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate,
@ -32,6 +30,8 @@ public:
virtual ~BoxAudioCodec();
virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
};
#endif // _BOX_AUDIO_CODEC_H

View File

@ -48,13 +48,13 @@ private:
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(100);
GetDisplay()->ShowNotification("Volume\n100");
GetDisplay()->ShowNotification("最大音量");
});
volume_down_button_.OnClick([this]() {
@ -64,13 +64,13 @@ private:
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(0);
GetDisplay()->ShowNotification("Volume\n0");
GetDisplay()->ShowNotification("已静音");
});
}

View File

@ -48,13 +48,13 @@ private:
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(100);
GetDisplay()->ShowNotification("Volume\n100");
GetDisplay()->ShowNotification("最大音量");
});
volume_down_button_.OnClick([this]() {
@ -64,13 +64,13 @@ private:
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(0);
GetDisplay()->ShowNotification("Volume\n0");
GetDisplay()->ShowNotification("已静音");
});
}

View File

@ -41,6 +41,7 @@ public:
virtual Mqtt* CreateMqtt() = 0;
virtual Udp* CreateUdp() = 0;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) = 0;
virtual const char* GetNetworkStateIcon() = 0;
virtual bool GetBatteryLevel(int &level, bool& charging);
virtual std::string GetJson();
virtual void SetPowerSaveMode(bool enabled) = 0;

View File

@ -11,7 +11,9 @@
#define BLINK_TASK_STOPPED_BIT BIT0
#define BLINK_TASK_RUNNING_BIT BIT1
#define DEFAULT_BRIGHTNESS 16
#define DEFAULT_BRIGHTNESS 4
#define HIGH_BRIGHTNESS 16
#define LOW_BRIGHTNESS 2
class Led {
public:

View File

@ -1,5 +1,6 @@
#include "ml307_board.h"
#include "application.h"
#include "font_awesome_symbols.h"
#include <esp_log.h>
#include <esp_timer.h>
@ -34,7 +35,7 @@ Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_si
void Ml307Board::StartNetwork() {
auto display = Board::GetInstance().GetDisplay();
display->SetText(std::string("Starting modem"));
display->SetStatus("初始化模块");
modem_.SetDebug(false);
modem_.SetBaudRate(921600);
@ -54,7 +55,7 @@ void Ml307Board::StartNetwork() {
void Ml307Board::WaitForNetworkReady() {
auto& application = Application::GetInstance();
auto display = Board::GetInstance().GetDisplay();
display->SetText(std::string("Wait for network\n"));
display->SetStatus("等待网络...");
int result = modem_.WaitForNetworkReady();
if (result == -1) {
application.Alert("Error", "PIN is not ready");
@ -103,6 +104,29 @@ bool Ml307Board::GetNetworkState(std::string& network_name, int& signal_quality,
return signal_quality != -1;
}
const char* Ml307Board::GetNetworkStateIcon() {
if (!modem_.network_ready()) {
return FONT_AWESOME_SIGNAL_OFF;
}
int csq = modem_.GetCsq();
if (csq == -1) {
return FONT_AWESOME_SIGNAL_OFF;
} else if (csq >= 0 && csq <= 9) {
return FONT_AWESOME_SIGNAL_1;
} else if (csq >= 10 && csq <= 14) {
return FONT_AWESOME_SIGNAL_2;
} else if (csq >= 15 && csq <= 19) {
return FONT_AWESOME_SIGNAL_3;
} else if (csq >= 20 && csq <= 24) {
return FONT_AWESOME_SIGNAL_4;
} else if (csq >= 25 && csq <= 31) {
return FONT_AWESOME_SIGNAL_FULL;
}
ESP_LOGW(TAG, "Invalid CSQ: %d", csq);
return FONT_AWESOME_SIGNAL_OFF;
}
std::string Ml307Board::GetBoardJson() {
// Set the board type for OTA
std::string board_type = BOARD_TYPE;

View File

@ -20,6 +20,7 @@ public:
virtual Mqtt* CreateMqtt() override;
virtual Udp* CreateUdp() override;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
};

View File

@ -1,6 +1,7 @@
#include "wifi_board.h"
#include "application.h"
#include "system_info.h"
#include "font_awesome_symbols.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
@ -38,7 +39,7 @@ void WifiBoard::StartNetwork() {
// Try to connect to WiFi, if failed, launch the WiFi configuration AP
auto& wifi_station = WifiStation::GetInstance();
display->SetText(std::string("Connect to WiFi\n") + wifi_station.GetSsid());
display->SetStatus(std::string("正在连接 ") + wifi_station.GetSsid());
wifi_station.Start();
if (!wifi_station.IsConnected()) {
builtin_led->SetBlue();
@ -46,10 +47,18 @@ void WifiBoard::StartNetwork() {
auto& wifi_ap = WifiConfigurationAp::GetInstance();
wifi_ap.SetSsidPrefix("Xiaozhi");
wifi_ap.Start();
// 播报配置 WiFi 的提示
application.Alert("Info", "Configuring WiFi");
// 显示 WiFi 配置 AP 的 SSID 和 Web 服务器 URL
display->SetText(wifi_ap.GetSsid() + "\n" + wifi_ap.GetWebServerUrl());
std::string hint = "请在手机上连接热点 ";
hint += wifi_ap.GetSsid();
hint += ",然后打开浏览器访问 ";
hint += wifi_ap.GetWebServerUrl();
display->SetStatus(hint);
// Wait forever until reset after configuration
while (true) {
vTaskDelay(pdMS_TO_TICKS(1000));
@ -103,6 +112,24 @@ bool WifiBoard::GetNetworkState(std::string& network_name, int& signal_quality,
return signal_quality != -1;
}
const char* WifiBoard::GetNetworkStateIcon() {
if (wifi_config_mode_) {
return FONT_AWESOME_WIFI;
}
auto& wifi_station = WifiStation::GetInstance();
if (!wifi_station.IsConnected()) {
return FONT_AWESOME_WIFI_OFF;
}
int8_t rssi = wifi_station.GetRssi();
if (rssi >= -55) {
return FONT_AWESOME_WIFI;
} else if (rssi >= -65) {
return FONT_AWESOME_WIFI_FAIR;
} else {
return FONT_AWESOME_WIFI_WEAK;
}
}
std::string WifiBoard::GetBoardJson() {
// Set the board type for OTA
auto& wifi_station = WifiStation::GetInstance();

View File

@ -17,6 +17,7 @@ public:
virtual Mqtt* CreateMqtt() override;
virtual Udp* CreateUdp() override;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
};

View File

@ -91,13 +91,13 @@ private:
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(100);
GetDisplay()->ShowNotification("Volume\n100");
GetDisplay()->ShowNotification("最大音量");
});
volume_down_button_.OnClick([this]() {
@ -107,13 +107,13 @@ private:
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(0);
GetDisplay()->ShowNotification("Volume\n0");
GetDisplay()->ShowNotification("已静音");
});
}

View File

@ -86,6 +86,10 @@ private:
Application::GetInstance().ToggleChatState();
});
boot_button_.OnLongPress([this]() {
axp2101_->PowerOff();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
@ -93,13 +97,13 @@ private:
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(100);
GetDisplay()->ShowNotification("Volume\n100");
GetDisplay()->ShowNotification("最大音量");
});
volume_down_button_.OnClick([this]() {
@ -109,13 +113,13 @@ private:
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
GetDisplay()->ShowNotification("音量 " + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
auto codec = GetAudioCodec();
codec->SetOutputVolume(0);
GetDisplay()->ShowNotification("Volume\n0");
GetDisplay()->ShowNotification("已静音");
});
}
@ -160,7 +164,6 @@ public:
virtual bool GetBatteryLevel(int &level, bool& charging) override {
level = axp2101_->GetBatteryLevel();
charging = axp2101_->IsCharging();
ESP_LOGI(TAG, "Battery level: %d, Charging: %d", level, charging);
return true;
}
};

View File

@ -1,136 +0,0 @@
#include <esp_log.h>
#include <esp_err.h>
#include <string>
#include <cstdlib>
#include "display.h"
#include "board.h"
#include "application.h"
#define TAG "Display"
void Display::SetupUI() {
if (disp_ == nullptr) {
return;
}
ESP_LOGI(TAG, "Setting up UI");
Lock();
label_ = lv_label_create(lv_disp_get_scr_act(disp_));
// lv_obj_set_style_text_font(label_, font_, 0);
lv_obj_set_style_text_color(label_, lv_color_black(), 0);
lv_label_set_text(label_, "Initializing...");
lv_obj_set_width(label_, disp_->driver->hor_res);
lv_obj_set_height(label_, disp_->driver->ver_res);
notification_ = lv_label_create(lv_disp_get_scr_act(disp_));
// lv_obj_set_style_text_font(notification_, font_, 0);
lv_obj_set_style_text_color(notification_, lv_color_black(), 0);
lv_label_set_text(notification_, "Notification\nTest");
lv_obj_set_width(notification_, disp_->driver->hor_res);
lv_obj_set_height(notification_, disp_->driver->ver_res);
lv_obj_set_style_opa(notification_, LV_OPA_MIN, 0);
Unlock();
// Create a timer to update the display every 10 seconds
esp_timer_create_args_t update_display_timer_args = {
.callback = [](void *arg) {
Display* display = static_cast<Display*>(arg);
display->UpdateDisplay();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "UpdateDisplay",
.skip_unhandled_events = false,
};
ESP_ERROR_CHECK(esp_timer_create(&update_display_timer_args, &update_display_timer_));
ESP_ERROR_CHECK(esp_timer_start_periodic(update_display_timer_, 10 * 1000000));
}
Display::~Display() {
if (notification_timer_ != nullptr) {
esp_timer_stop(notification_timer_);
esp_timer_delete(notification_timer_);
}
if (update_display_timer_ != nullptr) {
esp_timer_stop(update_display_timer_);
esp_timer_delete(update_display_timer_);
}
if (label_ != nullptr) {
lv_obj_del(label_);
lv_obj_del(notification_);
}
if (font_ != nullptr) {
lv_font_free(font_);
}
}
void Display::SetText(const std::string &text) {
if (label_ != nullptr) {
text_ = text;
Lock();
// Change the text of the label
lv_label_set_text(label_, text_.c_str());
Unlock();
}
}
void Display::ShowNotification(const std::string &text) {
if (notification_ != nullptr) {
Lock();
lv_label_set_text(notification_, text.c_str());
lv_obj_set_style_opa(notification_, LV_OPA_MAX, 0);
lv_obj_set_style_opa(label_, LV_OPA_MIN, 0);
Unlock();
if (notification_timer_ != nullptr) {
esp_timer_stop(notification_timer_);
esp_timer_delete(notification_timer_);
}
esp_timer_create_args_t timer_args = {
.callback = [](void *arg) {
Display *display = static_cast<Display*>(arg);
display->Lock();
lv_obj_set_style_opa(display->notification_, LV_OPA_MIN, 0);
lv_obj_set_style_opa(display->label_, LV_OPA_MAX, 0);
display->Unlock();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "Notification Timer",
.skip_unhandled_events = false,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &notification_timer_));
ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, 3000000));
}
}
void Display::UpdateDisplay() {
auto chat_state = Application::GetInstance().GetChatState();
if (chat_state == kChatStateIdle) {
std::string text;
auto& board = Board::GetInstance();
std::string network_name;
int signal_quality;
std::string signal_quality_text;
if (!board.GetNetworkState(network_name, signal_quality, signal_quality_text)) {
text = "No network";
} else {
text = network_name + "\n" + signal_quality_text;
if (std::abs(signal_quality) != 99) {
text += " (" + std::to_string(signal_quality) + ")";
}
}
int battery_level;
bool charging;
if (board.GetBatteryLevel(battery_level, charging)) {
text += "\nPower " + std::to_string(battery_level) + "%";
if (charging) {
text += " (Charging)";
}
}
SetText(text);
}
}

View File

@ -1,39 +0,0 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include <lvgl.h>
#include <esp_timer.h>
#include <string>
class Display {
public:
virtual ~Display();
void SetupUI();
void SetText(const std::string &text);
void ShowNotification(const std::string &text);
void UpdateDisplay();
int width() const { return width_; }
int height() const { return height_; }
protected:
lv_disp_t *disp_ = nullptr;
lv_font_t *font_ = nullptr;
lv_obj_t *label_ = nullptr;
lv_obj_t *notification_ = nullptr;
esp_timer_handle_t notification_timer_ = nullptr;
esp_timer_handle_t update_display_timer_ = nullptr;
int width_ = 0;
int height_ = 0;
std::string text_;
virtual void Lock() = 0;
virtual void Unlock() = 0;
};
#endif

192
main/display/display.cc Normal file
View File

@ -0,0 +1,192 @@
#include <esp_log.h>
#include <esp_err.h>
#include <string>
#include <cstdlib>
#include "display.h"
#include "board.h"
#include "application.h"
#include "font_awesome_symbols.h"
#include "audio_codec.h"
#define TAG "Display"
Display::Display() {
// Notification timer
esp_timer_create_args_t notification_timer_args = {
.callback = [](void *arg) {
Display *display = static_cast<Display*>(arg);
DisplayLockGuard lock(display);
lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "Notification Timer",
.skip_unhandled_events = false,
};
ESP_ERROR_CHECK(esp_timer_create(&notification_timer_args, &notification_timer_));
// Update display timer
esp_timer_create_args_t update_display_timer_args = {
.callback = [](void *arg) {
Display *display = static_cast<Display*>(arg);
display->Update();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "Update Display Timer",
.skip_unhandled_events = false,
};
ESP_ERROR_CHECK(esp_timer_create(&update_display_timer_args, &update_timer_));
ESP_ERROR_CHECK(esp_timer_start_periodic(update_timer_, 1000000));
}
Display::~Display() {
esp_timer_stop(notification_timer_);
esp_timer_stop(update_timer_);
esp_timer_delete(notification_timer_);
esp_timer_delete(update_timer_);
if (network_label_ != nullptr) {
lv_obj_del(network_label_);
lv_obj_del(notification_label_);
lv_obj_del(status_label_);
lv_obj_del(mute_label_);
lv_obj_del(battery_label_);
}
}
void Display::SetStatus(const std::string &status) {
if (status_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_label_set_text(status_label_, status.c_str());
}
void Display::ShowNotification(const std::string &notification, int duration_ms) {
if (notification_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_label_set_text(notification_label_, notification.c_str());
lv_obj_clear_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
esp_timer_stop(notification_timer_);
ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, duration_ms * 1000));
}
void Display::Update() {
if (mute_label_ == nullptr) {
return;
}
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
DisplayLockGuard lock(this);
// 如果静音状态改变,则更新图标
if (codec->output_volume() == 0 && !muted_) {
muted_ = true;
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_MUTE);
} else if (codec->output_volume() > 0 && muted_) {
muted_ = false;
lv_label_set_text(mute_label_, "");
}
// 更新电池图标
int battery_level;
bool charging;
const char* icon = nullptr;
if (board.GetBatteryLevel(battery_level, charging)) {
if (charging) {
icon = FONT_AWESOME_BATTERY_CHARGING;
} else {
const char* levels[] = {
FONT_AWESOME_BATTERY_EMPTY, // 0-19%
FONT_AWESOME_BATTERY_1, // 20-39%
FONT_AWESOME_BATTERY_2, // 40-59%
FONT_AWESOME_BATTERY_3, // 60-79%
FONT_AWESOME_BATTERY_FULL, // 80-99%
FONT_AWESOME_BATTERY_FULL, // 100%
};
icon = levels[battery_level / 20];
}
if (battery_icon_ != icon) {
battery_icon_ = icon;
lv_label_set_text(battery_label_, battery_icon_);
}
}
// 仅在聊天状态为空闲时,更新网络图标
auto chat_state = Application::GetInstance().GetChatState();
if (chat_state == kChatStateIdle || chat_state == kChatStateUnknown) {
icon = board.GetNetworkStateIcon();
if (network_icon_ != icon) {
network_icon_ = icon;
lv_label_set_text(network_label_, network_icon_);
}
}
}
void Display::SetEmotion(const std::string &emotion) {
if (emotion_label_ == nullptr) {
return;
}
struct Emotion {
const char* icon;
const char* text;
};
static const std::vector<Emotion> emotions = {
{FONT_AWESOME_EMOJI_NEUTRAL, "neutral"},
{FONT_AWESOME_EMOJI_HAPPY, "happy"},
{FONT_AWESOME_EMOJI_LAUGHING, "laughing"},
{FONT_AWESOME_EMOJI_FUNNY, "funny"},
{FONT_AWESOME_EMOJI_SAD, "sad"},
{FONT_AWESOME_EMOJI_ANGRY, "angry"},
{FONT_AWESOME_EMOJI_CRYING, "crying"},
{FONT_AWESOME_EMOJI_LOVING, "loving"},
{FONT_AWESOME_EMOJI_EMBARRASSED, "embarrassed"},
{FONT_AWESOME_EMOJI_SURPRISED, "surprised"},
{FONT_AWESOME_EMOJI_SHOCKED, "shocked"},
{FONT_AWESOME_EMOJI_THINKING, "thinking"},
{FONT_AWESOME_EMOJI_WINKING, "winking"},
{FONT_AWESOME_EMOJI_COOL, "cool"},
{FONT_AWESOME_EMOJI_RELAXED, "relaxed"},
{FONT_AWESOME_EMOJI_DELICIOUS, "delicious"},
{FONT_AWESOME_EMOJI_KISSY, "kissy"},
{FONT_AWESOME_EMOJI_CONFIDENT, "confident"},
{FONT_AWESOME_EMOJI_SLEEPY, "sleepy"},
{FONT_AWESOME_EMOJI_SILLY, "silly"},
{FONT_AWESOME_EMOJI_CONFUSED, "confused"}
};
DisplayLockGuard lock(this);
// 查找匹配的表情
auto it = std::find_if(emotions.begin(), emotions.end(),
[&emotion](const Emotion& e) { return e.text == emotion; });
// 如果找到匹配的表情就显示对应图标否则显示默认的neutral表情
if (it != emotions.end()) {
lv_label_set_text(emotion_label_, it->icon);
} else {
lv_label_set_text(emotion_label_, FONT_AWESOME_EMOJI_NEUTRAL);
}
}
void Display::SetIcon(const char* icon) {
if (emotion_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_label_set_text(emotion_label_, icon);
}
void Display::SetChatMessage(const std::string &role, const std::string &content) {
}

64
main/display/display.h Normal file
View File

@ -0,0 +1,64 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include <lvgl.h>
#include <esp_timer.h>
#include <string>
class Display {
public:
Display();
virtual ~Display();
virtual void SetStatus(const std::string &status);
virtual void ShowNotification(const std::string &notification, int duration_ms = 3000);
virtual void SetEmotion(const std::string &emotion);
virtual void SetChatMessage(const std::string &role, const std::string &content);
virtual void SetIcon(const char* icon);
int width() const { return width_; }
int height() const { return height_; }
protected:
int width_ = 0;
int height_ = 0;
lv_disp_t *disp_ = nullptr;
lv_obj_t *emotion_label_ = nullptr;
lv_obj_t *network_label_ = nullptr;
lv_obj_t *status_label_ = nullptr;
lv_obj_t *notification_label_ = nullptr;
lv_obj_t *mute_label_ = nullptr;
lv_obj_t *battery_label_ = nullptr;
const char* battery_icon_ = nullptr;
const char* network_icon_ = nullptr;
bool muted_ = false;
esp_timer_handle_t notification_timer_ = nullptr;
esp_timer_handle_t update_timer_ = nullptr;
friend class DisplayLockGuard;
virtual void Lock() = 0;
virtual void Unlock() = 0;
virtual void Update();
};
class DisplayLockGuard {
public:
DisplayLockGuard(Display *display) : display_(display) {
display_->Lock();
}
~DisplayLockGuard() {
display_->Unlock();
}
private:
Display *display_;
};
#endif

View File

@ -1,4 +1,5 @@
#include "ssd1306_display.h"
#include "font_awesome_symbols.h"
#include <esp_log.h>
#include <esp_err.h>
@ -8,6 +9,10 @@
#define TAG "Ssd1306Display"
LV_FONT_DECLARE(font_puhui_14_1);
LV_FONT_DECLARE(font_awesome_30_1);
LV_FONT_DECLARE(font_awesome_14_1);
Ssd1306Display::Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y)
: mirror_x_(mirror_x), mirror_y_(mirror_y) {
width_ = width;
@ -85,9 +90,33 @@ Ssd1306Display::Ssd1306Display(void* i2c_master_handle, int width, int height, b
};
disp_ = lvgl_port_add_disp(&display_cfg);
if (disp_ == nullptr) {
ESP_LOGE(TAG, "Failed to add display");
return;
}
if (height_ == 64) {
SetupUI_128x64();
} else {
SetupUI_128x32();
}
}
Ssd1306Display::~Ssd1306Display() {
if (content_ != nullptr) {
lv_obj_del(content_);
}
if (status_bar_ != nullptr) {
lv_obj_del(status_bar_);
}
if (side_bar_ != nullptr) {
lv_obj_del(side_bar_);
}
if (container_ != nullptr) {
lv_obj_del(container_);
}
if (panel_ != nullptr) {
esp_lcd_panel_del(panel_);
}
@ -104,3 +133,134 @@ void Ssd1306Display::Lock() {
void Ssd1306Display::Unlock() {
lvgl_port_unlock();
}
void Ssd1306Display::SetupUI_128x64() {
DisplayLockGuard lock(this);
auto screen = lv_disp_get_scr_act(disp_);
lv_obj_set_style_text_font(screen, &font_puhui_14_1, 0);
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
/* Container */
container_ = lv_obj_create(screen);
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(container_, 0, 0);
lv_obj_set_style_border_width(container_, 0, 0);
lv_obj_set_style_pad_row(container_, 0, 0);
/* Status bar */
status_bar_ = lv_obj_create(container_);
lv_obj_set_size(status_bar_, LV_HOR_RES, 18);
lv_obj_set_style_radius(status_bar_, 0, 0);
/* Content */
content_ = lv_obj_create(container_);
lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_radius(status_bar_, 0, 0);
lv_obj_set_width(content_, LV_HOR_RES);
lv_obj_set_flex_grow(content_, 1);
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
lv_obj_center(emotion_label_);
/* Status bar */
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_column(status_bar_, 0, 0);
network_label_ = lv_label_create(status_bar_);
lv_label_set_text(network_label_, "");
lv_obj_set_style_text_font(network_label_, &font_awesome_14_1, 0);
notification_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(notification_label_, 1);
lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text(notification_label_, "通知");
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
status_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(status_label_, 1);
lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_text(status_label_, "正在初始化");
lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
mute_label_ = lv_label_create(status_bar_);
lv_label_set_text(mute_label_, "");
lv_obj_set_style_text_font(mute_label_, &font_awesome_14_1, 0);
battery_label_ = lv_label_create(status_bar_);
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, &font_awesome_14_1, 0);
}
void Ssd1306Display::SetupUI_128x32() {
DisplayLockGuard lock(this);
auto screen = lv_disp_get_scr_act(disp_);
lv_obj_set_style_text_font(screen, &font_puhui_14_1, 0);
/* Container */
container_ = lv_obj_create(screen);
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(container_, 0, 0);
lv_obj_set_style_border_width(container_, 0, 0);
lv_obj_set_style_pad_column(container_, 0, 0);
/* Left side */
side_bar_ = lv_obj_create(container_);
lv_obj_set_flex_grow(side_bar_, 1);
lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(side_bar_, 0, 0);
lv_obj_set_style_border_width(side_bar_, 0, 0);
lv_obj_set_style_radius(side_bar_, 0, 0);
lv_obj_set_style_pad_row(side_bar_, 0, 0);
/* Emotion label on the right side */
content_ = lv_obj_create(container_);
lv_obj_set_size(content_, 32, 32);
lv_obj_set_style_pad_all(content_, 0, 0);
lv_obj_set_style_border_width(content_, 0, 0);
lv_obj_set_style_radius(content_, 0, 0);
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
lv_obj_center(emotion_label_);
/* Status bar */
status_bar_ = lv_obj_create(side_bar_);
lv_obj_set_size(status_bar_, LV_SIZE_CONTENT, 16);
lv_obj_set_style_radius(status_bar_, 0, 0);
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_column(status_bar_, 0, 0);
network_label_ = lv_label_create(status_bar_);
lv_label_set_text(network_label_, "");
lv_obj_set_style_text_font(network_label_, &font_awesome_14_1, 0);
mute_label_ = lv_label_create(status_bar_);
lv_label_set_text(mute_label_, "");
lv_obj_set_style_text_font(mute_label_, &font_awesome_14_1, 0);
battery_label_ = lv_label_create(status_bar_);
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, &font_awesome_14_1, 0);
status_label_ = lv_label_create(side_bar_);
lv_obj_set_flex_grow(status_label_, 1);
lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_text(status_label_, "正在初始化");
notification_label_ = lv_label_create(side_bar_);
lv_obj_set_flex_grow(notification_label_, 1);
lv_label_set_text(notification_label_, "通知");
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
}

View File

@ -13,9 +13,17 @@ private:
bool mirror_x_ = false;
bool mirror_y_ = false;
lv_obj_t* status_bar_ = nullptr;
lv_obj_t* content_ = nullptr;
lv_obj_t* container_ = nullptr;
lv_obj_t* side_bar_ = nullptr;
virtual void Lock() override;
virtual void Unlock() override;
void SetupUI_128x64();
void SetupUI_128x32();
public:
Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x = false, bool mirror_y = false);
~Ssd1306Display();

View File

@ -1,4 +1,5 @@
#include "st7789_display.h"
#include "font_awesome_symbols.h"
#include <esp_log.h>
#include <esp_err.h>
@ -9,6 +10,10 @@
#define TAG "St7789Display"
#define LCD_LEDC_CH LEDC_CHANNEL_0
LV_FONT_DECLARE(font_puhui_14_1);
LV_FONT_DECLARE(font_awesome_30_1);
LV_FONT_DECLARE(font_awesome_14_1);
St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
gpio_num_t backlight_pin, bool backlight_output_invert,
int width, int height, bool mirror_x, bool mirror_y, bool swap_xy)
@ -61,9 +66,24 @@ St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
disp_ = lvgl_port_add_disp(&display_cfg);
SetBacklight(100);
SetupUI();
}
St7789Display::~St7789Display() {
if (content_ != nullptr) {
lv_obj_del(content_);
}
if (status_bar_ != nullptr) {
lv_obj_del(status_bar_);
}
if (side_bar_ != nullptr) {
lv_obj_del(side_bar_);
}
if (container_ != nullptr) {
lv_obj_del(container_);
}
if (panel_ != nullptr) {
esp_lcd_panel_del(panel_);
}
@ -127,3 +147,66 @@ void St7789Display::Lock() {
void St7789Display::Unlock() {
lvgl_port_unlock();
}
void St7789Display::SetupUI() {
DisplayLockGuard lock(this);
auto screen = lv_disp_get_scr_act(disp_);
lv_obj_set_style_text_font(screen, &font_puhui_14_1, 0);
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
/* Container */
container_ = lv_obj_create(screen);
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(container_, 0, 0);
lv_obj_set_style_border_width(container_, 0, 0);
lv_obj_set_style_pad_row(container_, 0, 0);
/* Status bar */
status_bar_ = lv_obj_create(container_);
lv_obj_set_size(status_bar_, LV_HOR_RES, 18);
lv_obj_set_style_radius(status_bar_, 0, 0);
/* Content */
content_ = lv_obj_create(container_);
lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_radius(content_, 0, 0);
lv_obj_set_width(content_, LV_HOR_RES);
lv_obj_set_flex_grow(content_, 1);
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
lv_obj_center(emotion_label_);
/* Status bar */
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_column(status_bar_, 0, 0);
network_label_ = lv_label_create(status_bar_);
lv_label_set_text(network_label_, "");
lv_obj_set_style_text_font(network_label_, &font_awesome_14_1, 0);
notification_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(notification_label_, 1);
lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text(notification_label_, "通知");
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
status_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(status_label_, 1);
lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_text(status_label_, "正在初始化");
lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
mute_label_ = lv_label_create(status_bar_);
lv_label_set_text(mute_label_, "");
lv_obj_set_style_text_font(mute_label_, &font_awesome_14_1, 0);
battery_label_ = lv_label_create(status_bar_);
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, &font_awesome_14_1, 0);
}

View File

@ -16,9 +16,15 @@ private:
bool mirror_x_ = false;
bool mirror_y_ = false;
bool swap_xy_ = false;
lv_obj_t* status_bar_ = nullptr;
lv_obj_t* content_ = nullptr;
lv_obj_t* container_ = nullptr;
lv_obj_t* side_bar_ = nullptr;
void InitializeBacklight(gpio_num_t backlight_pin);
void SetBacklight(uint8_t brightness);
void SetupUI();
virtual void Lock() override;
virtual void Unlock() override;

7515
main/fonts/GB2312.TXT Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,565 @@
/*******************************************************************************
* Size: 14 px
* Bpp: 1
* Opts: --no-compress --no-prefilter --force-fast-kern-format --font fa-regular-400.ttf --format lvgl --lv-include lvgl.h --bpp 1 -o font_awesome_14_1.c --size 14 -r 0xf5a4,0xf118,0xf59b,0xf588,0xe384,0xf556,0xf5b3,0xf584,0xf579,0xe36b,0xe375,0xe39b,0xf4da,0xe398,0xe392,0xe372,0xf598,0xe409,0xe38d,0xe3a4,0xe36d,0xf240,0xf241,0xf242,0xf243,0xf244,0xf377,0xf376,0xf1eb,0xf6ab,0xf6aa,0xf6ac,0xf012,0xf68f,0xf68e,0xf68d,0xf68c,0xf695,0xf028,0xf6a8,0xf027,0xf6a9,0xf001,0xf00c,0xf00d,0xf011,0xf013,0xf1f8,0xf015,0xf03e,0xf044,0xf048,0xf051,0xf04b,0xf04c,0xf04d,0xf060,0xf061,0xf062,0xf063,0xf071,0xf0f3,0xf3c5,0xf0ac,0xf124,0xf7c2,0xf293,0xf075,0xe1ec,0xf007,0xe04b,0xf019
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl.h"
#endif
#ifndef FONT_AWESOME_14_1
#define FONT_AWESOME_14_1 1
#endif
#if FONT_AWESOME_14_1
/*-----------------
* BITMAPS
*----------------*/
/*Store the image of the glyphs*/
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
/* U+E04B "" */
0x2, 0x0, 0x20, 0x1f, 0x8a, 0x5, 0xad, 0x5a,
0x5, 0xaa, 0x53, 0xf8, 0x0, 0x0, 0x0, 0x7f,
0xec, 0x3, 0x8b, 0x1f, 0xff,
/* U+E1EC "" */
0x9, 0x40, 0x25, 0x3, 0xff, 0x8, 0x4, 0xe0,
0x1c, 0x89, 0x4e, 0x55, 0xcb, 0xd4, 0x68, 0x5b,
0x80, 0x72, 0x1, 0xf, 0xfc, 0x9, 0x40, 0x25,
0x0,
/* U+E36B "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x16, 0x32, 0xe0,
0x2e, 0x23, 0x18, 0x8c, 0x60, 0x1, 0x87, 0x7,
0x1c, 0x34, 0x70, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+E36D "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0xc8,
0xce, 0x23, 0x18, 0x0, 0x60, 0x1, 0x83, 0xc7,
0x30, 0x34, 0x80, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+E372 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0x8c,
0xc6, 0x40, 0x98, 0x0, 0x60, 0x1, 0x80, 0x26,
0x21, 0xb4, 0x7e, 0x88, 0x2a, 0x18, 0xa0, 0x38,
0x80,
/* U+E375 "" */
0xf, 0xc0, 0xe1, 0xc7, 0x8f, 0x98, 0x6, 0xfc,
0xee, 0x95, 0x5b, 0xd5, 0x66, 0x39, 0x80, 0x6,
0x1c, 0x14, 0xf8, 0x88, 0x4, 0x10, 0x20, 0x3f,
0x0,
/* U+E384 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x12, 0x12, 0xd8,
0x6e, 0x80, 0x58, 0x0, 0x60, 0x1, 0x9c, 0xe7,
0x0, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+E38D "" */
0x7, 0x9c, 0x7f, 0x33, 0x1, 0xd8, 0x38, 0xc0,
0x42, 0x3, 0xb8, 0x0, 0x60, 0x1, 0x9c, 0xe6,
0x0, 0x1c, 0x30, 0x50, 0xc2, 0x20, 0x18, 0x60,
0xc0, 0xfc, 0x0,
/* U+E392 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0xc0,
0xe, 0x73, 0x98, 0x0, 0x60, 0x1, 0x80, 0x7,
0x21, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+E398 "" */
0xf, 0xc0, 0x40, 0x82, 0x1, 0x1f, 0xfe, 0xff,
0xff, 0xf3, 0xfb, 0xcf, 0x60, 0x1, 0x80, 0x7,
0x21, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+E39B "" */
0xf, 0xc0, 0x41, 0x82, 0x81, 0x15, 0x82, 0x88,
0xce, 0x3, 0x18, 0x0, 0x63, 0x1, 0x82, 0x7,
0x84, 0x36, 0xf0, 0x9f, 0x4, 0x7c, 0x60, 0xee,
0x0,
/* U+E3A4 "" */
0xf, 0xc0, 0xe1, 0x87, 0x1d, 0x18, 0x76, 0xd9,
0x4f, 0xd2, 0x3f, 0xc7, 0x76, 0x3d, 0xc3, 0xef,
0x15, 0xa6, 0xc5, 0x8d, 0x1c, 0x1c, 0x60, 0x3f,
0x0,
/* U+E409 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x16, 0x32, 0xed,
0x6e, 0x94, 0x9b, 0xde, 0x60, 0x1, 0x9f, 0xe7,
0x7c, 0xb4, 0xe4, 0x89, 0xe4, 0x18, 0x60, 0x3f,
0x0,
/* U+F001 "" */
0x0, 0xc, 0x3, 0xf0, 0x7c, 0x43, 0x1, 0x8,
0x3c, 0x2f, 0x90, 0xf0, 0x42, 0x1, 0x8, 0x3c,
0x21, 0x1f, 0x84, 0x62, 0xe, 0x88, 0x1, 0xc0,
0x0,
/* U+F007 "" */
0xf, 0x1, 0x98, 0x10, 0x81, 0x8, 0x10, 0x81,
0xf8, 0xf, 0x0, 0x0, 0x1f, 0x86, 0x6, 0x40,
0x2c, 0x3, 0x80, 0x1f, 0xff,
/* U+F00C "" */
0x0, 0x10, 0x3, 0x0, 0x60, 0xc, 0xc1, 0x86,
0x30, 0x36, 0x1, 0xc0, 0x8, 0x0,
/* U+F00D "" */
0x81, 0xe1, 0x99, 0x87, 0x81, 0x81, 0xe1, 0x99,
0x86, 0x81, 0x80,
/* U+F011 "" */
0x2, 0x1, 0x11, 0x18, 0x8c, 0x84, 0x2c, 0x21,
0xc1, 0x6, 0x8, 0x30, 0x1, 0x80, 0xa, 0x0,
0x98, 0xc, 0x60, 0xc0, 0xf8, 0x0,
/* U+F012 "" */
0x0, 0x2, 0x0, 0x4, 0x0, 0x48, 0x0, 0x90,
0x1, 0x20, 0x22, 0x40, 0x44, 0x80, 0x89, 0x9,
0x12, 0x12, 0x26, 0x24, 0x4c, 0x48, 0x98, 0x91,
0x31, 0x22, 0x40,
/* U+F013 "" */
0xf, 0x80, 0x44, 0x1e, 0x3c, 0xa0, 0xac, 0x21,
0xe6, 0xcd, 0x22, 0x49, 0x32, 0xcf, 0x9e, 0x10,
0xd4, 0x14, 0xf1, 0xe0, 0x88, 0x3, 0x80,
/* U+F015 "" */
0x0, 0x0, 0x1, 0x80, 0x7, 0xc0, 0xc, 0x30,
0x18, 0x18, 0x30, 0xc, 0x60, 0x6, 0xe0, 0x7,
0x23, 0xc4, 0x22, 0x44, 0x22, 0x44, 0x22, 0x44,
0x22, 0x44, 0x22, 0x44, 0x1f, 0xf8,
/* U+F019 "" */
0x2, 0x0, 0x10, 0x0, 0x80, 0x4, 0x0, 0x20,
0x19, 0x30, 0x6b, 0x1, 0xf0, 0xf7, 0x7c, 0x10,
0x60, 0xb, 0x0, 0x58, 0x0, 0xff, 0xfc,
/* U+F027 "" */
0x3, 0x0, 0x70, 0xd, 0x7, 0x90, 0xf1, 0x38,
0x11, 0x81, 0x1f, 0x13, 0x79, 0x0, 0xd0, 0x7,
0x0, 0x30,
/* U+F028 "" */
0x0, 0x0, 0x1, 0x83, 0x1, 0xc0, 0xc1, 0xa1,
0xa7, 0x90, 0x6f, 0x89, 0x96, 0x4, 0x4b, 0x2,
0x25, 0xf1, 0x32, 0xbc, 0x82, 0xc3, 0x43, 0x40,
0xe0, 0x60, 0x30, 0x60,
/* U+F03E "" */
0xff, 0xfe, 0x0, 0x18, 0x0, 0x66, 0x1, 0x98,
0x6, 0x2, 0x18, 0x1c, 0x66, 0xf9, 0xbf, 0xe6,
0xff, 0xdf, 0xff, 0xff, 0xff,
/* U+F044 "" */
0x0, 0x10, 0x1, 0xe7, 0xcc, 0xe0, 0xea, 0x85,
0x1a, 0x8, 0xc8, 0x46, 0x22, 0x30, 0x89, 0x92,
0x3c, 0x48, 0x81, 0x20, 0x4, 0x80, 0x11, 0xff,
0x80,
/* U+F048 "" */
0x87, 0x1e, 0xef, 0x1c, 0x38, 0x78, 0xdd, 0x8f,
0xc,
/* U+F04B "" */
0x0, 0x38, 0xb, 0x82, 0x30, 0x87, 0x20, 0x68,
0xe, 0x3, 0x83, 0xa3, 0x89, 0x83, 0xc0, 0xc0,
0x0,
/* U+F04C "" */
0xf7, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
0x95, 0xf7,
/* U+F04D "" */
0xff, 0xe0, 0x18, 0x6, 0x1, 0x80, 0x60, 0x18,
0x6, 0x1, 0x80, 0x7f, 0xf0,
/* U+F051 "" */
0xc3, 0xc6, 0xec, 0x78, 0x70, 0xe3, 0xdd, 0xe3,
0x84,
/* U+F060 "" */
0x4, 0x0, 0xc0, 0x18, 0x3, 0x0, 0x60, 0xf,
0xff, 0x60, 0x3, 0x0, 0x18, 0x0, 0xc0, 0x4,
0x0,
/* U+F061 "" */
0x0, 0x0, 0x30, 0x1, 0x80, 0xc, 0x0, 0x6f,
0xff, 0x0, 0x60, 0xc, 0x1, 0x80, 0x30, 0x0,
0x0,
/* U+F062 "" */
0xe, 0x1, 0xc0, 0x54, 0x12, 0xc6, 0x4c, 0x88,
0x81, 0x0, 0x20, 0x4, 0x0, 0x80, 0x10, 0x2,
0x0,
/* U+F063 "" */
0x4, 0x0, 0x80, 0x10, 0x2, 0x0, 0x40, 0x8,
0x31, 0x1b, 0x26, 0x35, 0x83, 0xe0, 0x38, 0x2,
0x0,
/* U+F071 "" */
0x3, 0x0, 0x1e, 0x1, 0x48, 0x2, 0x90, 0x1a,
0x61, 0xc8, 0x82, 0x21, 0x18, 0x6, 0x42, 0xb,
0x8, 0x38, 0x0, 0x7f, 0xff,
/* U+F075 "" */
0xf, 0xc0, 0xc0, 0xc4, 0x0, 0xb0, 0x3, 0x80,
0x6, 0x0, 0x18, 0x0, 0x70, 0x3, 0x40, 0x9,
0x0, 0xcf, 0xfc, 0x30, 0x0,
/* U+F0AC "" */
0xf, 0xc0, 0xf3, 0xc6, 0xc9, 0x92, 0x12, 0xff,
0xfe, 0x21, 0x18, 0x84, 0x62, 0x11, 0xff, 0xfd,
0x21, 0x26, 0x49, 0x8f, 0x3c, 0x1f, 0xe0,
/* U+F0F3 "" */
0x6, 0x0, 0xf0, 0x10, 0x82, 0x4, 0x20, 0x42,
0x4, 0x20, 0x42, 0x4, 0x60, 0x64, 0x2, 0xff,
0xf0, 0x0, 0xf, 0x0, 0x60,
/* U+F118 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0xc8,
0xce, 0x23, 0x18, 0x0, 0x60, 0x1, 0x80, 0x7,
0x21, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+F124 "" */
0x0, 0x20, 0xf, 0x7, 0xa1, 0xe6, 0x78, 0x4f,
0xec, 0x2, 0xc0, 0x38, 0x3, 0x80, 0x30, 0x3,
0x0, 0x20,
/* U+F1EB "" */
0x7, 0xf8, 0xe, 0x3, 0x86, 0x0, 0x3b, 0x0,
0x2, 0x0, 0x0, 0x0, 0xfc, 0x0, 0xc1, 0xc0,
0x40, 0x10, 0x0, 0x0, 0x0, 0x70, 0x0, 0x1c,
0x0, 0x2, 0x0,
/* U+F1F8 "" */
0xf, 0x81, 0x88, 0xff, 0xf4, 0x2, 0x40, 0x24,
0x2, 0x60, 0x26, 0x2, 0x60, 0x26, 0x2, 0x60,
0x62, 0x6, 0x20, 0x63, 0xfc,
/* U+F240 "" */
0x7f, 0xfc, 0x80, 0x2, 0xbf, 0xf3, 0xbf, 0xf3,
0xbf, 0xf3, 0x80, 0x2, 0x80, 0x2, 0x7f, 0xfc,
/* U+F241 "" */
0x7f, 0xfc, 0x80, 0x2, 0xbf, 0x83, 0xbf, 0x83,
0xbf, 0x83, 0x80, 0x2, 0x80, 0x2, 0x7f, 0xfc,
/* U+F242 "" */
0x7f, 0xfc, 0x80, 0x2, 0x9f, 0x3, 0x9f, 0x3,
0x9f, 0x3, 0x80, 0x2, 0x80, 0x2, 0x7f, 0xfc,
/* U+F243 "" */
0x7f, 0xfc, 0x80, 0x2, 0xb8, 0x3, 0xb8, 0x3,
0xb8, 0x3, 0x80, 0x2, 0x80, 0x2, 0x7f, 0xfc,
/* U+F244 "" */
0x7f, 0xfc, 0x80, 0x2, 0x80, 0x3, 0x80, 0x3,
0x80, 0x3, 0x80, 0x2, 0x80, 0x2, 0x7f, 0xfc,
/* U+F293 "" */
0xc, 0x7, 0x2, 0xd9, 0x36, 0xb1, 0xf0, 0x70,
0x38, 0x7f, 0x64, 0xc2, 0xc1, 0xc0, 0xc0, 0x40,
/* U+F376 "" */
0x7e, 0xdc, 0x81, 0x82, 0x83, 0x83, 0x87, 0xc3,
0x87, 0xc3, 0x83, 0x82, 0x83, 0x2, 0x76, 0xfc,
/* U+F377 "" */
0x80, 0x0, 0x30, 0x0, 0x3, 0x0, 0x0, 0x7f,
0xf8, 0x4e, 0x1, 0x10, 0xc0, 0x64, 0x18, 0x19,
0x3, 0x86, 0x40, 0x31, 0x10, 0x7, 0x43, 0xfe,
0x60, 0x0, 0xc, 0x0, 0x1, 0xc0, 0x0, 0x10,
/* U+F3C5 "" */
0x1e, 0x18, 0x64, 0xa, 0x31, 0x92, 0x64, 0x98,
0xc5, 0x2, 0x40, 0x88, 0x43, 0x30, 0x48, 0x1c,
0x3, 0x0,
/* U+F4DA "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0xc8,
0xce, 0x22, 0x98, 0x0, 0x60, 0x1, 0x80, 0x7,
0x21, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+F556 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0xc0,
0xe, 0x73, 0x98, 0x84, 0x60, 0x1, 0x80, 0x7,
0xc, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+F579 "" */
0xf, 0xc0, 0x61, 0xc2, 0x1, 0x90, 0x2, 0xdc,
0xee, 0x94, 0x5b, 0xb5, 0x67, 0x39, 0x80, 0x7,
0x3e, 0x34, 0x0, 0x88, 0x6, 0x18, 0x70, 0x3f,
0x0,
/* U+F584 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0xc4,
0x8e, 0x73, 0x98, 0xcc, 0x60, 0x1, 0x80, 0x7,
0x3f, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+F588 "" */
0x3, 0xf0, 0x1, 0x86, 0x1, 0x80, 0x60, 0x40,
0x8, 0x33, 0x33, 0x9, 0xce, 0x40, 0x0, 0x3,
0x80, 0x7, 0xe0, 0x1, 0xf4, 0xfc, 0xb1, 0x1e,
0x20, 0x60, 0x18, 0xe, 0x1c, 0x0, 0xfc, 0x0,
/* U+F598 "" */
0xf, 0xc0, 0x60, 0x82, 0x1, 0x90, 0x2, 0x88,
0x6, 0x23, 0x98, 0x0, 0x60, 0xc1, 0x83, 0x36,
0xc, 0xf4, 0x33, 0xc8, 0xf, 0x10, 0x0, 0x3f,
0x0,
/* U+F59B "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0xa, 0xd8,
0x6e, 0x33, 0x19, 0x2, 0x60, 0x1, 0x8f, 0xe7,
0x3f, 0x34, 0x78, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+F5A4 "" */
0xf, 0xc0, 0x61, 0x82, 0x1, 0x10, 0x2, 0xc8,
0xce, 0x23, 0x18, 0x0, 0x60, 0x1, 0x80, 0x7,
0x0, 0x34, 0x0, 0x88, 0x4, 0x18, 0x60, 0x3f,
0x0,
/* U+F5B3 "" */
0xf, 0xc0, 0x40, 0x82, 0x1, 0x10, 0x2, 0x80,
0x6, 0x73, 0x98, 0x0, 0x65, 0xc9, 0xd7, 0x2f,
0x5c, 0xb7, 0x3, 0x8c, 0xc, 0x18, 0x60, 0x3f,
0x0,
/* U+F68C "" */
0xf0,
/* U+F68D "" */
0x8, 0x63, 0x18, 0xc4,
/* U+F68E "" */
0x0, 0x80, 0x40, 0x21, 0x10, 0x8c, 0x46, 0x23,
0x11, 0x88, 0x80,
/* U+F68F "" */
0x0, 0x10, 0x1, 0x0, 0x10, 0x11, 0x1, 0x10,
0x11, 0x9, 0x10, 0x91, 0x89, 0x18, 0x91, 0x89,
0x18, 0x91,
/* U+F695 "" */
0xc0, 0x1, 0x30, 0x0, 0x8c, 0x2, 0x43, 0x1,
0x20, 0x60, 0x90, 0x1c, 0x48, 0x6, 0x24, 0x1,
0xd2, 0x4, 0x39, 0x2, 0x4c, 0x91, 0x23, 0xc8,
0x90, 0xe4, 0x48, 0x9a, 0x24, 0x44,
/* U+F6A8 "" */
0x3, 0x0, 0xe, 0x0, 0x34, 0x33, 0xc8, 0x3f,
0x13, 0x30, 0x22, 0x60, 0x44, 0xf8, 0x99, 0x79,
0x4, 0x1a, 0x18, 0x1c, 0x0, 0x18, 0x0,
/* U+F6A9 "" */
0x3, 0x0, 0xe, 0x0, 0x34, 0x3, 0xc9, 0x1f,
0x13, 0x70, 0x23, 0xa0, 0x47, 0x78, 0x9b, 0x79,
0x22, 0x1a, 0x0, 0x1c, 0x0, 0x18, 0x0,
/* U+F6AA "" */
0xfd, 0x0,
/* U+F6AB "" */
0x1f, 0x86, 0xe, 0x80, 0x20, 0x0, 0x6, 0x0,
0xe0, 0x6, 0x0,
/* U+F6AC "" */
0x80, 0x0, 0x37, 0xfc, 0xe, 0x3, 0x93, 0x0,
0x78, 0xe0, 0x8, 0x1c, 0x0, 0x27, 0xc0, 0x39,
0xb8, 0x10, 0x34, 0x0, 0xc, 0x0, 0x73, 0x0,
0x38, 0xe0, 0x8, 0x18, 0x0, 0x4,
/* U+F7C2 "" */
0x1f, 0xc8, 0x14, 0x96, 0x35, 0x80, 0x60, 0x18,
0x6, 0x1, 0x80, 0x60, 0x18, 0x6, 0x1, 0x80,
0x7f, 0xf0
};
/*---------------------
* GLYPH DESCRIPTION
*--------------------*/
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
{.bitmap_index = 0, .adv_w = 196, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 21, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 46, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 71, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 96, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 121, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 146, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 171, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 198, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 223, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 248, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 273, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 298, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 323, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 348, .adv_w = 196, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 369, .adv_w = 196, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 383, .adv_w = 168, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = 1},
{.bitmap_index = 394, .adv_w = 224, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 416, .adv_w = 280, .box_w = 15, .box_h = 14, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 443, .adv_w = 224, .box_w = 13, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 466, .adv_w = 252, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 496, .adv_w = 224, .box_w = 13, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 519, .adv_w = 196, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 537, .adv_w = 280, .box_w = 17, .box_h = 13, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 565, .adv_w = 224, .box_w = 14, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 586, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 611, .adv_w = 140, .box_w = 7, .box_h = 10, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 620, .adv_w = 168, .box_w = 10, .box_h = 13, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 637, .adv_w = 140, .box_w = 8, .box_h = 10, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 647, .adv_w = 168, .box_w = 10, .box_h = 10, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 660, .adv_w = 140, .box_w = 7, .box_h = 10, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 669, .adv_w = 196, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 686, .adv_w = 196, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 703, .adv_w = 168, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 720, .adv_w = 168, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 737, .adv_w = 224, .box_w = 14, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 758, .adv_w = 224, .box_w = 14, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 779, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 802, .adv_w = 196, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 823, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 848, .adv_w = 196, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 866, .adv_w = 280, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 893, .adv_w = 196, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 914, .adv_w = 252, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 930, .adv_w = 252, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 946, .adv_w = 252, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 962, .adv_w = 252, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 978, .adv_w = 252, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 994, .adv_w = 168, .box_w = 9, .box_h = 14, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1010, .adv_w = 252, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 1026, .adv_w = 280, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1058, .adv_w = 168, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1076, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1101, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1126, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1151, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1176, .adv_w = 280, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1208, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1233, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1258, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1283, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1308, .adv_w = 280, .box_w = 1, .box_h = 4, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1309, .adv_w = 280, .box_w = 5, .box_h = 6, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1313, .adv_w = 280, .box_w = 9, .box_h = 9, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1324, .adv_w = 280, .box_w = 12, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1342, .adv_w = 280, .box_w = 17, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1372, .adv_w = 252, .box_w = 15, .box_h = 12, .ofs_x = 1, .ofs_y = -1},
{.bitmap_index = 1395, .adv_w = 252, .box_w = 15, .box_h = 12, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 1418, .adv_w = 280, .box_w = 3, .box_h = 3, .ofs_x = 7, .ofs_y = -1},
{.bitmap_index = 1420, .adv_w = 280, .box_w = 12, .box_h = 7, .ofs_x = 3, .ofs_y = -1},
{.bitmap_index = 1431, .adv_w = 280, .box_w = 17, .box_h = 14, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1461, .adv_w = 168, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -2}
};
/*---------------------
* CHARACTER MAPPING
*--------------------*/
static const uint16_t unicode_list_0[] = {
0x0, 0x1a1, 0x320, 0x322, 0x327, 0x32a, 0x339, 0x342,
0x347, 0x34d, 0x350, 0x359, 0x3be, 0xfb6, 0xfbc, 0xfc1,
0xfc2, 0xfc6, 0xfc7, 0xfc8, 0xfca, 0xfce, 0xfdc, 0xfdd,
0xff3, 0xff9, 0xffd, 0x1000, 0x1001, 0x1002, 0x1006, 0x1015,
0x1016, 0x1017, 0x1018, 0x1026, 0x102a, 0x1061, 0x10a8, 0x10cd,
0x10d9, 0x11a0, 0x11ad, 0x11f5, 0x11f6, 0x11f7, 0x11f8, 0x11f9,
0x1248, 0x132b, 0x132c, 0x137a, 0x148f, 0x150b, 0x152e, 0x1539,
0x153d, 0x154d, 0x1550, 0x1559, 0x1568, 0x1641, 0x1642, 0x1643,
0x1644, 0x164a, 0x165d, 0x165e, 0x165f, 0x1660, 0x1661, 0x1777
};
/*Collect the unicode lists and glyph_id offsets*/
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
{
.range_start = 57419, .range_length = 6008, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 72, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
}
};
/*--------------------
* ALL CUSTOM DATA
*--------------------*/
#if LVGL_VERSION_MAJOR == 8
/*Store all the custom data of the font*/
static lv_font_fmt_txt_glyph_cache_t cache;
#endif
#if LVGL_VERSION_MAJOR >= 8
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = glyph_dsc,
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.bpp = 1,
.kern_classes = 0,
.bitmap_format = 0,
#if LVGL_VERSION_MAJOR == 8
.cache = &cache
#endif
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
const lv_font_t font_awesome_14_1 = {
#else
lv_font_t font_awesome_14_1 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 15, /*The maximum line height required by the font*/
.base_line = 2, /*Baseline measured from the bottom of the line*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = LV_FONT_SUBPX_NONE,
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = -1,
.underline_thickness = 1,
#endif
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
.fallback = NULL,
#endif
.user_data = NULL,
};
#endif /*#if FONT_AWESOME_14_1*/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
#ifndef FONT_AWESOME_SYMBOLS_H
#define FONT_AWESOME_SYMBOLS_H
#define FONT_AWESOME_EMOJI_NEUTRAL "\xef\x96\xa4"
#define FONT_AWESOME_EMOJI_HAPPY "\xef\x84\x98"
#define FONT_AWESOME_EMOJI_LAUGHING "\xef\x96\x9b"
#define FONT_AWESOME_EMOJI_FUNNY "\xef\x96\x88"
#define FONT_AWESOME_EMOJI_SAD "\xee\x8e\x84"
#define FONT_AWESOME_EMOJI_ANGRY "\xef\x95\x96"
#define FONT_AWESOME_EMOJI_CRYING "\xef\x96\xb3"
#define FONT_AWESOME_EMOJI_LOVING "\xef\x96\x84"
#define FONT_AWESOME_EMOJI_EMBARRASSED "\xef\x95\xb9"
#define FONT_AWESOME_EMOJI_SURPRISED "\xee\x8d\xab"
#define FONT_AWESOME_EMOJI_SHOCKED "\xee\x8d\xb5"
#define FONT_AWESOME_EMOJI_THINKING "\xee\x8e\x9b"
#define FONT_AWESOME_EMOJI_WINKING "\xef\x93\x9a"
#define FONT_AWESOME_EMOJI_COOL "\xee\x8e\x98"
#define FONT_AWESOME_EMOJI_RELAXED "\xee\x8e\x92"
#define FONT_AWESOME_EMOJI_DELICIOUS "\xee\x8d\xb2"
#define FONT_AWESOME_EMOJI_KISSY "\xef\x96\x98"
#define FONT_AWESOME_EMOJI_CONFIDENT "\xee\x90\x89"
#define FONT_AWESOME_EMOJI_SLEEPY "\xee\x8e\x8d"
#define FONT_AWESOME_EMOJI_SILLY "\xee\x8e\xa4"
#define FONT_AWESOME_EMOJI_CONFUSED "\xee\x8d\xad"
#define FONT_AWESOME_BATTERY_FULL "\xef\x89\x80"
#define FONT_AWESOME_BATTERY_3 "\xef\x89\x81"
#define FONT_AWESOME_BATTERY_2 "\xef\x89\x82"
#define FONT_AWESOME_BATTERY_1 "\xef\x89\x83"
#define FONT_AWESOME_BATTERY_EMPTY "\xef\x89\x84"
#define FONT_AWESOME_BATTERY_SLASH "\xef\x8d\xb7"
#define FONT_AWESOME_BATTERY_CHARGING "\xef\x8d\xb6"
#define FONT_AWESOME_WIFI "\xef\x87\xab"
#define FONT_AWESOME_WIFI_FAIR "\xef\x9a\xab"
#define FONT_AWESOME_WIFI_WEAK "\xef\x9a\xaa"
#define FONT_AWESOME_WIFI_OFF "\xef\x9a\xac"
#define FONT_AWESOME_SIGNAL_FULL "\xef\x80\x92"
#define FONT_AWESOME_SIGNAL_4 "\xef\x9a\x8f"
#define FONT_AWESOME_SIGNAL_3 "\xef\x9a\x8e"
#define FONT_AWESOME_SIGNAL_2 "\xef\x9a\x8d"
#define FONT_AWESOME_SIGNAL_1 "\xef\x9a\x8c"
#define FONT_AWESOME_SIGNAL_OFF "\xef\x9a\x95"
#define FONT_AWESOME_VOLUME_HIGH "\xef\x80\xa8"
#define FONT_AWESOME_VOLUME_MEDIUM "\xef\x9a\xa8"
#define FONT_AWESOME_VOLUME_LOW "\xef\x80\xa7"
#define FONT_AWESOME_VOLUME_MUTE "\xef\x9a\xa9"
#define FONT_AWESOME_MUSIC "\xef\x80\x81"
#define FONT_AWESOME_CHECK "\xef\x80\x8c"
#define FONT_AWESOME_XMARK "\xef\x80\x8d"
#define FONT_AWESOME_POWER "\xef\x80\x91"
#define FONT_AWESOME_GEAR "\xef\x80\x93"
#define FONT_AWESOME_TRASH "\xef\x87\xb8"
#define FONT_AWESOME_HOME "\xef\x80\x95"
#define FONT_AWESOME_IMAGE "\xef\x80\xbe"
#define FONT_AWESOME_EDIT "\xef\x81\x84"
#define FONT_AWESOME_PREV "\xef\x81\x88"
#define FONT_AWESOME_NEXT "\xef\x81\x91"
#define FONT_AWESOME_PLAY "\xef\x81\x8b"
#define FONT_AWESOME_PAUSE "\xef\x81\x8c"
#define FONT_AWESOME_STOP "\xef\x81\x8d"
#define FONT_AWESOME_MICARROW_LEFT "\xef\x81\xa0"
#define FONT_AWESOME_ARROW_RIGHT "\xef\x81\xa1"
#define FONT_AWESOME_ARROW_UP "\xef\x81\xa2"
#define FONT_AWESOME_ARROW_DOWN "\xef\x81\xa3"
#define FONT_AWESOME_WARNING "\xef\x81\xb1"
#define FONT_AWESOME_BELL "\xef\x83\xb3"
#define FONT_AWESOME_LOCATION "\xef\x8f\x85"
#define FONT_AWESOME_GLOBE "\xef\x82\xac"
#define FONT_AWESOME_LOCATION_ARROW "\xef\x84\xa4"
#define FONT_AWESOME_SD_CARD "\xef\x9f\x82"
#define FONT_AWESOME_BLUETOOTH "\xef\x8a\x93"
#define FONT_AWESOME_COMMENT "\xef\x81\xb5"
#define FONT_AWESOME_AI_CHIP "\xee\x87\xac"
#define FONT_AWESOME_USER "\xef\x80\x87"
#define FONT_AWESOME_USER_ROBOT "\xee\x81\x8b"
#define FONT_AWESOME_DOWNLOAD "\xef\x80\x99"
#endif

48889
main/fonts/font_puhui_14_1.c Normal file

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
dependencies:
78/esp-wifi-connect: "~1.4.0"
78/esp-opus-encoder: "~1.1.0"
78/esp-ml307: "~1.6.0"
78/esp-ml307: "~1.6.3"
espressif/led_strip: "^2.4.1"
espressif/esp_codec_dev: "^1.3.1"
espressif/esp-sr: "^1.9.0"

View File

@ -36,8 +36,8 @@ void Ota::SetPostData(const std::string& post_data) {
}
bool Ota::CheckVersion() {
std::string current_version = esp_app_get_description()->version;
ESP_LOGI(TAG, "Current version: %s", current_version.c_str());
current_version_ = esp_app_get_description()->version;
ESP_LOGI(TAG, "Current version: %s", current_version_.c_str());
if (check_version_url_.length() < 10) {
ESP_LOGE(TAG, "Check version URL is not properly set");
@ -109,7 +109,7 @@ bool Ota::CheckVersion() {
cJSON_Delete(root);
// Check if the version is newer, for example, 0.1.0 is newer than 0.0.1
has_new_version_ = IsNewVersionAvailable(current_version, firmware_version_);
has_new_version_ = IsNewVersionAvailable(current_version_, firmware_version_);
if (has_new_version_) {
ESP_LOGI(TAG, "New version available: %s", firmware_version_.c_str());
} else {

View File

@ -19,10 +19,14 @@ public:
void StartUpgrade(std::function<void(int progress, size_t speed)> callback);
void MarkCurrentVersionValid();
const std::string& GetFirmwareVersion() const { return firmware_version_; }
const std::string& GetCurrentVersion() const { return current_version_; }
private:
std::string check_version_url_;
bool has_new_version_ = false;
bool has_mqtt_config_ = false;
std::string current_version_;
std::string firmware_version_;
std::string firmware_url_;
std::string post_data_;