forked from xiaozhi/xiaozhi-esp32
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9de52647c | ||
|
|
ff28586c35 | ||
|
|
e4382faee3 | ||
|
|
b07ec1a148 | ||
|
|
472219d5bf | ||
|
|
aa806f676e | ||
|
|
2d7a127c27 | ||
|
|
c79d6cf4d8 | ||
|
|
bb43cf5876 | ||
|
|
874adc80b8 | ||
|
|
6dcc64459f | ||
|
|
6bfe2719a8 | ||
|
|
794e6f4bef | ||
|
|
cabb29a1bb | ||
|
|
a494c41367 | ||
|
|
384426e03a | ||
|
|
15891f5840 |
@@ -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.1")
|
||||
set(PROJECT_VER "0.9.2")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(xiaozhi)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- Wi-Fi 配网
|
||||
- 支持 BOOT 键唤醒和打断
|
||||
- 离线语音唤醒(乐鑫方案)
|
||||
- 流式语音对话(WebSocket 协议)
|
||||
- 流式语音对话(WebSocket 或 UDP 协议)
|
||||
- 支持国语、粤语、英语、日语、韩语 5 种语言识别(SenseVoice 方案)
|
||||
- 声纹识别(识别是谁在喊 AI 的名字,[3D Speaker 项目](https://github.com/modelscope/3D-Speaker))
|
||||
- 使用大模型 TTS(火山引擎与 CosyVoice 方案)
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
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"
|
||||
"boards/wifi_board.cc"
|
||||
"boards/ml307_board.cc"
|
||||
"protocol.cc"
|
||||
"protocols/protocol.cc"
|
||||
"protocols/mqtt_protocol.cc"
|
||||
"protocols/websocket_protocol.cc"
|
||||
"system_info.cc"
|
||||
"system_reset.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)
|
||||
list(APPEND SOURCES ${BOARD_COMMON_SOURCES})
|
||||
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common)
|
||||
|
||||
# 根据 BOARD_TYPE 配置添加对应的板级文件
|
||||
if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI)
|
||||
@@ -28,8 +34,6 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
|
||||
set(BOARD_TYPE "bread-compact-ml307")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
|
||||
set(BOARD_TYPE "esp-box-3")
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_0)
|
||||
set(BOARD_TYPE "kevin-box-0")
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_1)
|
||||
set(BOARD_TYPE "kevin-box-1")
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2)
|
||||
|
||||
@@ -6,13 +6,26 @@ config OTA_VERSION_URL
|
||||
help
|
||||
The application will access this URL to check for updates.
|
||||
|
||||
choice CONNECTION_TYPE
|
||||
prompt "Connection Type"
|
||||
default CONNECTION_TYPE_MQTT_UDP
|
||||
help
|
||||
网络数据传输协议
|
||||
config CONNECTION_TYPE_MQTT_UDP
|
||||
bool "MQTT + UDP"
|
||||
config CONNECTION_TYPE_WEBSOCKET
|
||||
bool "Websocket"
|
||||
endchoice
|
||||
|
||||
config WEBSOCKET_URL
|
||||
depends on CONNECTION_TYPE_WEBSOCKET
|
||||
string "Websocket URL"
|
||||
default "wss://api.tenclass.net/xiaozhi/v1/"
|
||||
help
|
||||
Communication with the server through websocket after wake up.
|
||||
|
||||
config WEBSOCKET_ACCESS_TOKEN
|
||||
depends on CONNECTION_TYPE_WEBSOCKET
|
||||
string "Websocket Access Token"
|
||||
default "test-token"
|
||||
help
|
||||
@@ -29,8 +42,6 @@ choice BOARD_TYPE
|
||||
bool "面包板新版接线(ML307 AT)"
|
||||
config BOARD_TYPE_ESP_BOX_3
|
||||
bool "ESP BOX 3"
|
||||
config BOARD_TYPE_KEVIN_BOX_0
|
||||
bool "Kevin Box 0"
|
||||
config BOARD_TYPE_KEVIN_BOX_1
|
||||
bool "Kevin Box 1"
|
||||
config BOARD_TYPE_KEVIN_BOX_2
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#include "system_info.h"
|
||||
#include "ml307_ssl_transport.h"
|
||||
#include "audio_codec.h"
|
||||
#include "protocols/mqtt_protocol.h"
|
||||
#include "mqtt_protocol.h"
|
||||
#include "websocket_protocol.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <esp_log.h>
|
||||
@@ -42,23 +44,31 @@ 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
|
||||
@@ -66,6 +76,7 @@ void Application::CheckNewVersion() {
|
||||
SetChatState(kChatStateIdle);
|
||||
} else {
|
||||
ota_.MarkCurrentVersionValid();
|
||||
display->ShowNotification("版本 " + ota_.GetCurrentVersion());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -76,9 +87,9 @@ void Application::CheckNewVersion() {
|
||||
}
|
||||
|
||||
void Application::Alert(const std::string&& title, const std::string&& message) {
|
||||
ESP_LOGE(TAG, "Alert: %s, %s", title.c_str(), message.c_str());
|
||||
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);
|
||||
@@ -112,20 +123,52 @@ void Application::ToggleChatState() {
|
||||
Schedule([this]() {
|
||||
if (chat_state_ == kChatStateIdle) {
|
||||
SetChatState(kChatStateConnecting);
|
||||
if (protocol_->OpenAudioChannel()) {
|
||||
opus_encoder_.ResetState();
|
||||
SetChatState(kChatStateListening);
|
||||
} else {
|
||||
if (!protocol_->OpenAudioChannel()) {
|
||||
ESP_LOGE(TAG, "Failed to open audio channel");
|
||||
SetChatState(kChatStateIdle);
|
||||
return;
|
||||
}
|
||||
|
||||
keep_listening_ = true;
|
||||
protocol_->SendStartListening(kListeningModeAutoStop);
|
||||
SetChatState(kChatStateListening);
|
||||
} else if (chat_state_ == kChatStateSpeaking) {
|
||||
AbortSpeaking();
|
||||
AbortSpeaking(kAbortReasonNone);
|
||||
} else if (chat_state_ == kChatStateListening) {
|
||||
protocol_->CloseAudioChannel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Application::StartListening() {
|
||||
Schedule([this]() {
|
||||
keep_listening_ = false;
|
||||
if (chat_state_ == kChatStateIdle) {
|
||||
if (!protocol_->IsAudioChannelOpened()) {
|
||||
SetChatState(kChatStateConnecting);
|
||||
if (!protocol_->OpenAudioChannel()) {
|
||||
SetChatState(kChatStateIdle);
|
||||
ESP_LOGE(TAG, "Failed to open audio channel");
|
||||
return;
|
||||
}
|
||||
}
|
||||
protocol_->SendStartListening(kListeningModeManualStop);
|
||||
SetChatState(kChatStateListening);
|
||||
} else if (chat_state_ == kChatStateSpeaking) {
|
||||
AbortSpeaking(kAbortReasonNone);
|
||||
protocol_->SendStartListening(kListeningModeManualStop);
|
||||
SetChatState(kChatStateListening);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Application::StopListening() {
|
||||
Schedule([this]() {
|
||||
protocol_->SendStopListening();
|
||||
SetChatState(kChatStateIdle);
|
||||
});
|
||||
}
|
||||
|
||||
void Application::Start() {
|
||||
auto& board = Board::GetInstance();
|
||||
board.Initialize();
|
||||
@@ -136,7 +179,6 @@ void Application::Start() {
|
||||
|
||||
/* Setup the display */
|
||||
auto display = board.GetDisplay();
|
||||
display->SetupUI();
|
||||
|
||||
/* Setup the audio codec */
|
||||
auto codec = board.GetAudioCodec();
|
||||
@@ -229,35 +271,40 @@ 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
wake_word_detect_.OnWakeWordDetected([this]() {
|
||||
Schedule([this]() {
|
||||
wake_word_detect_.OnWakeWordDetected([this](const std::string& wake_word) {
|
||||
Schedule([this, &wake_word]() {
|
||||
if (chat_state_ == kChatStateIdle) {
|
||||
SetChatState(kChatStateConnecting);
|
||||
wake_word_detect_.EncodeWakeWordData();
|
||||
|
||||
if (protocol_->OpenAudioChannel()) {
|
||||
std::string opus;
|
||||
// Encode and send the wake word data to the server
|
||||
while (wake_word_detect_.GetWakeWordOpus(opus)) {
|
||||
protocol_->SendAudio(opus);
|
||||
}
|
||||
opus_encoder_.ResetState();
|
||||
// Send a ready message to indicate the server that the wake word data is sent
|
||||
SetChatState(kChatStateWakeWordDetected);
|
||||
} else {
|
||||
if (!protocol_->OpenAudioChannel()) {
|
||||
ESP_LOGE(TAG, "Failed to open audio channel");
|
||||
SetChatState(kChatStateIdle);
|
||||
wake_word_detect_.StartDetection();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string opus;
|
||||
// Encode and send the wake word data to the server
|
||||
while (wake_word_detect_.GetWakeWordOpus(opus)) {
|
||||
protocol_->SendAudio(opus);
|
||||
}
|
||||
// Set the chat state to wake word detected
|
||||
protocol_->SendWakeWordDetected(wake_word);
|
||||
ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str());
|
||||
keep_listening_ = true;
|
||||
SetChatState(kChatStateListening);
|
||||
} else if (chat_state_ == kChatStateSpeaking) {
|
||||
AbortSpeaking();
|
||||
AbortSpeaking(kAbortReasonWakeWordDetected);
|
||||
}
|
||||
|
||||
// Resume detection
|
||||
@@ -268,68 +315,87 @@ void Application::Start() {
|
||||
#endif
|
||||
|
||||
// Initialize the protocol
|
||||
display->SetText("Starting\nProtocol...");
|
||||
display->SetStatus("初始化协议");
|
||||
#ifdef CONFIG_CONNECTION_TYPE_WEBSOCKET
|
||||
protocol_ = new WebsocketProtocol();
|
||||
#else
|
||||
protocol_ = new MqttProtocol();
|
||||
#endif
|
||||
protocol_->OnNetworkError([this](const std::string& message) {
|
||||
Alert("Error", std::move(message));
|
||||
});
|
||||
protocol_->OnIncomingAudio([this](const std::string& data) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
audio_decode_queue_.emplace_back(std::move(data));
|
||||
cv_.notify_all();
|
||||
});
|
||||
protocol_->OnAudioChannelOpened([this, codec]() {
|
||||
if (protocol_->GetServerSampleRate() != codec->output_sample_rate()) {
|
||||
protocol_->OnAudioChannelOpened([this, codec, &board]() {
|
||||
if (protocol_->server_sample_rate() != codec->output_sample_rate()) {
|
||||
ESP_LOGW(TAG, "服务器的音频采样率 %d 与设备输出的采样率 %d 不一致,重采样后可能会失真",
|
||||
protocol_->GetServerSampleRate(), codec->output_sample_rate());
|
||||
protocol_->server_sample_rate(), codec->output_sample_rate());
|
||||
}
|
||||
SetDecodeSampleRate(protocol_->GetServerSampleRate());
|
||||
SetDecodeSampleRate(protocol_->server_sample_rate());
|
||||
board.SetPowerSaveMode(false);
|
||||
});
|
||||
protocol_->OnAudioChannelClosed([this]() {
|
||||
protocol_->OnAudioChannelClosed([this, &board]() {
|
||||
Schedule([this]() {
|
||||
SetChatState(kChatStateIdle);
|
||||
});
|
||||
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) {
|
||||
auto state = cJSON_GetObjectItem(root, "state");
|
||||
if (strcmp(state->valuestring, "start") == 0) {
|
||||
Schedule([this]() {
|
||||
skip_to_end_ = false;
|
||||
SetChatState(kChatStateSpeaking);
|
||||
if (chat_state_ == kChatStateIdle || chat_state_ == kChatStateListening) {
|
||||
skip_to_end_ = false;
|
||||
opus_decoder_ctl(opus_decoder_, OPUS_RESET_STATE);
|
||||
SetChatState(kChatStateSpeaking);
|
||||
}
|
||||
});
|
||||
} else if (strcmp(state->valuestring, "stop") == 0) {
|
||||
Schedule([this]() {
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
codec->WaitForOutputDone();
|
||||
if (chat_state_ == kChatStateSpeaking) {
|
||||
SetChatState(kChatStateListening);
|
||||
if (keep_listening_) {
|
||||
protocol_->SendStartListening(kListeningModeAutoStop);
|
||||
SetChatState(kChatStateListening);
|
||||
} else {
|
||||
SetChatState(kChatStateIdle);
|
||||
}
|
||||
}
|
||||
});
|
||||
} 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) {
|
||||
@@ -354,9 +420,9 @@ void Application::MainLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
void Application::AbortSpeaking() {
|
||||
void Application::AbortSpeaking(AbortReason reason) {
|
||||
ESP_LOGI(TAG, "Abort speaking");
|
||||
protocol_->SendAbort();
|
||||
protocol_->SendAbortSpeaking(reason);
|
||||
|
||||
skip_to_end_ = true;
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
@@ -370,8 +436,6 @@ void Application::SetChatState(ChatState state) {
|
||||
"connecting",
|
||||
"listening",
|
||||
"speaking",
|
||||
"wake_word_detected",
|
||||
"testing",
|
||||
"upgrading",
|
||||
"invalid_state"
|
||||
};
|
||||
@@ -379,16 +443,15 @@ void Application::SetChatState(ChatState state) {
|
||||
// No need to update the state
|
||||
return;
|
||||
}
|
||||
chat_state_ = state;
|
||||
ESP_LOGI(TAG, "STATE: %s", state_str[chat_state_]);
|
||||
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
auto builtin_led = Board::GetInstance().GetBuiltinLed();
|
||||
switch (chat_state_) {
|
||||
switch (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
|
||||
@@ -396,12 +459,14 @@ 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");
|
||||
opus_encoder_.ResetState();
|
||||
#ifdef CONFIG_USE_AFE_SR
|
||||
audio_processor_.Start();
|
||||
#endif
|
||||
@@ -409,22 +474,22 @@ 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
|
||||
break;
|
||||
case kChatStateWakeWordDetected:
|
||||
builtin_led->SetBlue();
|
||||
builtin_led->TurnOn();
|
||||
break;
|
||||
case kChatStateUpgrading:
|
||||
builtin_led->SetGreen();
|
||||
builtin_led->StartContinuousBlink(100);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid chat state: %d", chat_state_);
|
||||
return;
|
||||
}
|
||||
|
||||
protocol_->SendState(state_str[chat_state_]);
|
||||
chat_state_ = state;
|
||||
ESP_LOGI(TAG, "STATE: %s", state_str[chat_state_]);
|
||||
}
|
||||
|
||||
void Application::AudioEncodeTask() {
|
||||
|
||||
@@ -22,12 +22,6 @@
|
||||
#include "audio_processor.h"
|
||||
#endif
|
||||
|
||||
struct BinaryProtocol3 {
|
||||
uint8_t type;
|
||||
uint8_t reserved;
|
||||
uint16_t payload_size;
|
||||
uint8_t payload[];
|
||||
} __attribute__((packed));
|
||||
|
||||
enum ChatState {
|
||||
kChatStateUnknown,
|
||||
@@ -35,7 +29,6 @@ enum ChatState {
|
||||
kChatStateConnecting,
|
||||
kChatStateListening,
|
||||
kChatStateSpeaking,
|
||||
kChatStateWakeWordDetected,
|
||||
kChatStateUpgrading
|
||||
};
|
||||
|
||||
@@ -47,17 +40,19 @@ public:
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
void Start();
|
||||
ChatState GetChatState() const { return chat_state_; }
|
||||
void Schedule(std::function<void()> callback);
|
||||
void SetChatState(ChatState state);
|
||||
void Alert(const std::string&& title, const std::string&& message);
|
||||
void AbortSpeaking();
|
||||
void AbortSpeaking(AbortReason reason);
|
||||
void ToggleChatState();
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
void StartListening();
|
||||
void StopListening();
|
||||
|
||||
private:
|
||||
Application();
|
||||
@@ -74,6 +69,7 @@ private:
|
||||
Protocol* protocol_ = nullptr;
|
||||
EventGroupHandle_t event_group_;
|
||||
volatile ChatState chat_state_ = kChatStateUnknown;
|
||||
bool keep_listening_ = false;
|
||||
bool skip_to_end_ = false;
|
||||
|
||||
// Audio encode / decode
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -65,8 +65,6 @@ NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_n
|
||||
};
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
|
||||
ESP_LOGI(TAG, "Duplex channels created");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "boards/ml307_board.h"
|
||||
#include "ml307_board.h"
|
||||
#include "audio_codecs/no_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "system_reset.h"
|
||||
@@ -18,6 +18,7 @@ private:
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
SystemReset system_reset_;
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
@@ -47,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]() {
|
||||
@@ -63,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("已静音");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,13 +78,14 @@ public:
|
||||
CompactMl307Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096),
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
|
||||
system_reset_(RESET_NVS_BUTTON_GPIO, RESET_FACTORY_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing CompactMl307Board");
|
||||
// Check if the reset button is pressed
|
||||
SystemReset::GetInstance().CheckButtons();
|
||||
system_reset_.CheckButtons();
|
||||
|
||||
InitializeDisplayI2c();
|
||||
InitializeButtons();
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
|
||||
#define RESET_NVS_BUTTON_GPIO GPIO_NUM_1
|
||||
#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_2
|
||||
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_41
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_42
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "boards/wifi_board.h"
|
||||
#include "wifi_board.h"
|
||||
#include "audio_codecs/no_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "system_reset.h"
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
@@ -18,6 +19,7 @@ private:
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
SystemReset system_reset_;
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
@@ -37,7 +39,11 @@ private:
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetChatState() == kChatStateUnknown && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
@@ -47,13 +53,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]() {
|
||||
@@ -63,13 +69,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("已静音");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,13 +83,14 @@ public:
|
||||
CompactWifiBoard() :
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
|
||||
system_reset_(RESET_NVS_BUTTON_GPIO, RESET_FACTORY_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing CompactWifiBoard");
|
||||
// Check if the reset button is pressed
|
||||
SystemReset::GetInstance().CheckButtons();
|
||||
system_reset_.CheckButtons();
|
||||
|
||||
InitializeDisplayI2c();
|
||||
InitializeButtons();
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
|
||||
#define RESET_NVS_BUTTON_GPIO GPIO_NUM_1
|
||||
#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_2
|
||||
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_41
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_42
|
||||
|
||||
@@ -41,8 +41,10 @@ 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;
|
||||
};
|
||||
|
||||
#define DECLARE_BOARD(BOARD_CLASS_NAME) \
|
||||
@@ -30,15 +30,28 @@ Button::~Button() {
|
||||
}
|
||||
}
|
||||
|
||||
void Button::OnPress(std::function<void()> callback) {
|
||||
void Button::OnPressDown(std::function<void()> callback) {
|
||||
if (button_handle_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
on_press_ = callback;
|
||||
on_press_down_ = callback;
|
||||
iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, [](void* handle, void* usr_data) {
|
||||
Button* button = static_cast<Button*>(usr_data);
|
||||
if (button->on_press_) {
|
||||
button->on_press_();
|
||||
if (button->on_press_down_) {
|
||||
button->on_press_down_();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
void Button::OnPressUp(std::function<void()> callback) {
|
||||
if (button_handle_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
on_press_up_ = callback;
|
||||
iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, [](void* handle, void* usr_data) {
|
||||
Button* button = static_cast<Button*>(usr_data);
|
||||
if (button->on_press_up_) {
|
||||
button->on_press_up_();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
@@ -10,7 +10,8 @@ public:
|
||||
Button(gpio_num_t gpio_num);
|
||||
~Button();
|
||||
|
||||
void OnPress(std::function<void()> callback);
|
||||
void OnPressDown(std::function<void()> callback);
|
||||
void OnPressUp(std::function<void()> callback);
|
||||
void OnLongPress(std::function<void()> callback);
|
||||
void OnClick(std::function<void()> callback);
|
||||
void OnDoubleClick(std::function<void()> callback);
|
||||
@@ -19,7 +20,8 @@ private:
|
||||
button_handle_t button_handle_;
|
||||
|
||||
|
||||
std::function<void()> on_press_;
|
||||
std::function<void()> on_press_down_;
|
||||
std::function<void()> on_press_up_;
|
||||
std::function<void()> on_long_press_;
|
||||
std::function<void()> on_click_;
|
||||
std::function<void()> on_double_click_;
|
||||
31
main/boards/common/i2c_device.cc
Normal file
31
main/boards/common/i2c_device.cc
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "I2cDevice"
|
||||
|
||||
|
||||
I2cDevice::I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr) {
|
||||
i2c_device_config_t i2c_device_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = addr,
|
||||
.scl_speed_hz = 100000,
|
||||
.scl_wait_us = 0,
|
||||
.flags = {
|
||||
.disable_ack_check = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &i2c_device_cfg, &i2c_device_));
|
||||
assert(i2c_device_ != NULL);
|
||||
}
|
||||
|
||||
void I2cDevice::WriteReg(uint8_t reg, uint8_t value) {
|
||||
uint8_t buffer[2] = {reg, value};
|
||||
ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100));
|
||||
}
|
||||
|
||||
uint8_t I2cDevice::ReadReg(uint8_t reg) {
|
||||
uint8_t buffer[1];
|
||||
ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, 1, 100));
|
||||
return buffer[0];
|
||||
}
|
||||
17
main/boards/common/i2c_device.h
Normal file
17
main/boards/common/i2c_device.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef I2C_DEVICE_H
|
||||
#define I2C_DEVICE_H
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
class I2cDevice {
|
||||
public:
|
||||
I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
|
||||
|
||||
protected:
|
||||
i2c_master_dev_handle_t i2c_device_;
|
||||
|
||||
void WriteReg(uint8_t reg, uint8_t value);
|
||||
uint8_t ReadReg(uint8_t reg);
|
||||
};
|
||||
|
||||
#endif // I2C_DEVICE_H
|
||||
@@ -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:
|
||||
@@ -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;
|
||||
@@ -114,3 +138,7 @@ std::string Ml307Board::GetBoardJson() {
|
||||
board_json += "\"iccid\":\"" + modem_.GetIccid() + "\"}";
|
||||
return board_json;
|
||||
}
|
||||
|
||||
void Ml307Board::SetPowerSaveMode(bool enabled) {
|
||||
// TODO: Implement power save mode for ML307
|
||||
}
|
||||
@@ -20,6 +20,8 @@ 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;
|
||||
};
|
||||
|
||||
#endif // ML307_BOARD_H
|
||||
@@ -11,12 +11,12 @@
|
||||
#define TAG "SystemReset"
|
||||
|
||||
|
||||
SystemReset::SystemReset() {
|
||||
SystemReset::SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin) : reset_nvs_pin_(reset_nvs_pin), reset_factory_pin_(reset_factory_pin) {
|
||||
// Configure GPIO1, GPIO2 as INPUT, reset NVS flash if the button is pressed
|
||||
gpio_config_t io_conf;
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << GPIO_NUM_1) | (1ULL << GPIO_NUM_2);
|
||||
io_conf.pin_bit_mask = (1ULL << reset_nvs_pin_) | (1ULL << reset_factory_pin_);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
@@ -24,13 +24,13 @@ SystemReset::SystemReset() {
|
||||
|
||||
|
||||
void SystemReset::CheckButtons() {
|
||||
if (gpio_get_level(GPIO_NUM_2) == 0) {
|
||||
if (gpio_get_level(reset_factory_pin_) == 0) {
|
||||
ESP_LOGI(TAG, "Button is pressed, reset to factory");
|
||||
ResetNvsFlash();
|
||||
ResetToFactory();
|
||||
}
|
||||
|
||||
if (gpio_get_level(GPIO_NUM_1) == 0) {
|
||||
if (gpio_get_level(reset_nvs_pin_) == 0) {
|
||||
ESP_LOGI(TAG, "Button is pressed, reset NVS flash");
|
||||
ResetNvsFlash();
|
||||
}
|
||||
21
main/boards/common/system_reset.h
Normal file
21
main/boards/common/system_reset.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef _SYSTEM_RESET_H
|
||||
#define _SYSTEM_RESET_H
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
class SystemReset {
|
||||
public:
|
||||
SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin); // 构造函数私有化
|
||||
void CheckButtons();
|
||||
|
||||
private:
|
||||
gpio_num_t reset_nvs_pin_;
|
||||
gpio_num_t reset_factory_pin_;
|
||||
|
||||
void ResetNvsFlash();
|
||||
void ResetToFactory();
|
||||
void RestartInSeconds(int seconds);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "wifi_board.h"
|
||||
#include "application.h"
|
||||
#include "system_info.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
@@ -38,7 +40,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 +48,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));
|
||||
@@ -66,12 +76,15 @@ Http* WifiBoard::CreateHttp() {
|
||||
}
|
||||
|
||||
WebSocket* WifiBoard::CreateWebSocket() {
|
||||
#ifdef CONFIG_CONNECTION_TYPE_WEBSOCKET
|
||||
std::string url = CONFIG_WEBSOCKET_URL;
|
||||
if (url.find("wss://") == 0) {
|
||||
return new WebSocket(new TlsTransport());
|
||||
} else {
|
||||
return new WebSocket(new TcpTransport());
|
||||
}
|
||||
return new WebSocket(new TlsTransport());
|
||||
} else {
|
||||
return new WebSocket(new TcpTransport());
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Mqtt* WifiBoard::CreateMqtt() {
|
||||
@@ -100,6 +113,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();
|
||||
@@ -114,3 +145,20 @@ std::string WifiBoard::GetBoardJson() {
|
||||
board_json += "\"mac\":\"" + SystemInfo::GetMacAddress() + "\"}";
|
||||
return board_json;
|
||||
}
|
||||
|
||||
void WifiBoard::SetPowerSaveMode(bool enabled) {
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
wifi_station.SetPowerSaveMode(enabled);
|
||||
}
|
||||
|
||||
void WifiBoard::ResetWifiConfiguration() {
|
||||
// Reset the wifi station
|
||||
{
|
||||
Settings settings("wifi", true);
|
||||
settings.EraseAll();
|
||||
}
|
||||
GetDisplay()->ShowNotification("已重置 WiFi...");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
// Reboot the device
|
||||
esp_restart();
|
||||
}
|
||||
@@ -17,6 +17,9 @@ 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;
|
||||
virtual void ResetWifiConfiguration();
|
||||
};
|
||||
|
||||
#endif // WIFI_BOARD_H
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "boards/wifi_board.h"
|
||||
#include "wifi_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/no_display.h"
|
||||
#include "application.h"
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_47
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_48
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_45
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_21
|
||||
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_40
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_39
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_38
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_8
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_6
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_7
|
||||
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_4
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_5
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 64
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
|
||||
#define ML307_RX_PIN GPIO_NUM_17
|
||||
#define ML307_TX_PIN GPIO_NUM_16
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
@@ -1,156 +0,0 @@
|
||||
#include "boards/ml307_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_spiffs.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2c_master.h>
|
||||
|
||||
static const char *TAG = "KevinBoxBoard";
|
||||
|
||||
class KevinBoxBoard : public Ml307Board {
|
||||
private:
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
|
||||
void MountStorage() {
|
||||
// Mount the storage partition
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/storage",
|
||||
.partition_label = "storage",
|
||||
.max_files = 5,
|
||||
.format_if_mount_failed = true,
|
||||
};
|
||||
esp_vfs_spiffs_register(&conf);
|
||||
}
|
||||
|
||||
void Enable4GModule() {
|
||||
// Make GPIO15 HIGH to enable the 4G module
|
||||
gpio_config_t ml307_enable_config = {
|
||||
.pin_bit_mask = (1ULL << 15),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&ml307_enable_config);
|
||||
gpio_set_level(GPIO_NUM_15, 1);
|
||||
}
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = DISPLAY_SDA_PIN,
|
||||
.scl_io_num = DISPLAY_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_1,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(100);
|
||||
GetDisplay()->ShowNotification("Volume\n100");
|
||||
});
|
||||
|
||||
volume_down_button_.OnClick([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() - 10;
|
||||
if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification("Volume\n" + std::to_string(volume));
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
codec->SetOutputVolume(0);
|
||||
GetDisplay()->ShowNotification("Volume\n0");
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096),
|
||||
boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
|
||||
InitializeDisplayI2c();
|
||||
InitializeCodecI2c();
|
||||
MountStorage();
|
||||
Enable4GModule();
|
||||
|
||||
InitializeButtons();
|
||||
|
||||
Ml307Board::Initialize();
|
||||
}
|
||||
|
||||
virtual Led* GetBuiltinLed() override {
|
||||
static Led led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
return &display;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(KevinBoxBoard);
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "boards/ml307_board.h"
|
||||
#include "ml307_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "application.h"
|
||||
@@ -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("已静音");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +1,46 @@
|
||||
#include "axp2101.h"
|
||||
#include "board.h"
|
||||
#include "display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "AXP2101";
|
||||
#define TAG "Axp2101"
|
||||
|
||||
bool Axp2101::Initialize(i2c_master_bus_handle_t i2c_bus, int i2c_device_address) {
|
||||
i2c_device_config_t axp2101_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = (uint16_t)i2c_device_address,
|
||||
.scl_speed_hz = 100000,
|
||||
.scl_wait_us = 0,
|
||||
.flags = {
|
||||
.disable_ack_check = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &axp2101_cfg, &i2c_device_));
|
||||
Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
// ** EFUSE defaults **
|
||||
WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
|
||||
WriteReg(0x27, 0x10); // hold 4s to power off
|
||||
|
||||
WriteReg(0x93, 0x1c); // 配置aldo2输出为3.3v
|
||||
WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V
|
||||
|
||||
uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0
|
||||
uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0
|
||||
value = value | 0x02; // set bit 1 (ALDO2)
|
||||
WriteReg(0x90, value); // and power channels now enabled
|
||||
|
||||
WriteReg(0x64, 0x03); // CV charger voltage setting to 42V
|
||||
value = ReadReg(0x62);
|
||||
ESP_LOGI(TAG, "axp2101 read 0x62 get: 0x%X", value);
|
||||
WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V
|
||||
|
||||
WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA
|
||||
WriteReg(0x62, 0x10); // set Main battery charger current to 900mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
|
||||
WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
|
||||
WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA
|
||||
value = ReadReg(0x62);
|
||||
ESP_LOGI(TAG, "axp2101 read 0x62 get: 0x%X", value);
|
||||
|
||||
value = ReadReg(0x18);
|
||||
ESP_LOGI(TAG, "axp2101 read 0x18 get: 0x%X", value);
|
||||
value = value & 0b11100000;
|
||||
value = value | 0b00001110;
|
||||
WriteReg(0x18, value);
|
||||
value = ReadReg(0x18);
|
||||
ESP_LOGI(TAG, "axp2101 read 0x18 get: 0x%X", value);
|
||||
|
||||
WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables
|
||||
WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables
|
||||
WriteReg(0x16, 0x05); // set input voltage limit to 3.88v, for poor USB cables
|
||||
WriteReg(0x16, 0x05); // set input current limit to 2000mA
|
||||
|
||||
WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery)
|
||||
WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Axp2101::WriteReg(uint8_t reg, uint8_t value) {
|
||||
uint8_t buffer[2];
|
||||
buffer[0] = reg;
|
||||
buffer[1] = value;
|
||||
ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100));
|
||||
}
|
||||
|
||||
uint8_t Axp2101::ReadReg(uint8_t reg) {
|
||||
uint8_t buffer[1];
|
||||
ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, 1, 100));
|
||||
return buffer[0];
|
||||
int Axp2101::GetBatteryCurrentDirection() {
|
||||
return (ReadReg(0x01) & 0b01100000) >> 5;
|
||||
}
|
||||
|
||||
bool Axp2101::IsCharging() {
|
||||
uint8_t value = ReadReg(0x01);
|
||||
return (value & 0b01100000) == 0b00100000;
|
||||
return GetBatteryCurrentDirection() == 1;
|
||||
}
|
||||
|
||||
bool Axp2101::IsDischarging() {
|
||||
return GetBatteryCurrentDirection() == 2;
|
||||
}
|
||||
|
||||
bool Axp2101::IsChargingDone() {
|
||||
@@ -73,8 +49,7 @@ bool Axp2101::IsChargingDone() {
|
||||
}
|
||||
|
||||
int Axp2101::GetBatteryLevel() {
|
||||
uint8_t value = ReadReg(0xA4);
|
||||
return value;
|
||||
return ReadReg(0xA4);
|
||||
}
|
||||
|
||||
void Axp2101::PowerOff() {
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
#ifndef __AXP2101_H__
|
||||
#define __AXP2101_H__
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include "i2c_device.h"
|
||||
|
||||
class Axp2101 {
|
||||
class Axp2101 : public I2cDevice {
|
||||
public:
|
||||
Axp2101() = default;
|
||||
bool Initialize(i2c_master_bus_handle_t i2c_bus, int i2c_device_address);
|
||||
Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
|
||||
bool IsCharging();
|
||||
bool IsDischarging();
|
||||
bool IsChargingDone();
|
||||
int GetBatteryLevel();
|
||||
void PowerOff();
|
||||
|
||||
private:
|
||||
i2c_master_dev_handle_t i2c_device_ = nullptr;
|
||||
|
||||
void WriteReg(uint8_t reg, uint8_t value);
|
||||
uint8_t ReadReg(uint8_t reg);
|
||||
int GetBatteryCurrentDirection();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "boards/ml307_board.h"
|
||||
#include "ml307_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/ssd1306_display.h"
|
||||
#include "application.h"
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <esp_spiffs.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
static const char *TAG = "KevinBoxBoard";
|
||||
|
||||
@@ -18,11 +19,46 @@ class KevinBoxBoard : public Ml307Board {
|
||||
private:
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Axp2101 axp2101_;
|
||||
Axp2101* axp2101_ = nullptr;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
uint8_t _data_buffer[2];
|
||||
esp_timer_handle_t power_save_timer_ = nullptr;
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
esp_timer_create_args_t power_save_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
auto board = static_cast<KevinBoxBoard*>(arg);
|
||||
board->PowerSaveCheck();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "Power Save Timer",
|
||||
.skip_unhandled_events = false,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000));
|
||||
}
|
||||
|
||||
void PowerSaveCheck() {
|
||||
// 电池放电模式下,如果待机超过一定时间,则自动关机
|
||||
const int seconds_to_shutdown = 600;
|
||||
static int seconds = 0;
|
||||
if (Application::GetInstance().GetChatState() != kChatStateIdle) {
|
||||
seconds = 0;
|
||||
return;
|
||||
}
|
||||
if (!axp2101_->IsDischarging()) {
|
||||
seconds = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
seconds++;
|
||||
if (seconds >= seconds_to_shutdown) {
|
||||
axp2101_->PowerOff();
|
||||
}
|
||||
}
|
||||
|
||||
void MountStorage() {
|
||||
// Mount the storage partition
|
||||
@@ -82,8 +118,15 @@ private:
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
// 测试按住说话
|
||||
// boot_button_.OnClick([this]() {
|
||||
// Application::GetInstance().ToggleChatState();
|
||||
// });
|
||||
boot_button_.OnPressDown([this]() {
|
||||
Application::GetInstance().StartListening();
|
||||
});
|
||||
boot_button_.OnPressUp([this]() {
|
||||
Application::GetInstance().StopListening();
|
||||
});
|
||||
|
||||
volume_up_button_.OnClick([this]() {
|
||||
@@ -93,13 +136,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 +152,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("已静音");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -130,12 +173,13 @@ public:
|
||||
ESP_LOGI(TAG, "Initializing KevinBoxBoard");
|
||||
InitializeDisplayI2c();
|
||||
InitializeCodecI2c();
|
||||
axp2101_.Initialize(codec_i2c_bus_, AXP2101_I2C_ADDR);
|
||||
axp2101_ = new Axp2101(codec_i2c_bus_, AXP2101_I2C_ADDR);
|
||||
|
||||
MountStorage();
|
||||
Enable4GModule();
|
||||
|
||||
InitializeButtons();
|
||||
InitializePowerSaveTimer();
|
||||
|
||||
Ml307Board::Initialize();
|
||||
}
|
||||
@@ -158,9 +202,15 @@ 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);
|
||||
static int last_level = 0;
|
||||
static bool last_charging = false;
|
||||
level = axp2101_->GetBatteryLevel();
|
||||
charging = axp2101_->IsCharging();
|
||||
if (level != last_level || charging != last_charging) {
|
||||
last_level = level;
|
||||
last_charging = charging;
|
||||
ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
@@ -1,22 +1,43 @@
|
||||
#include "boards/wifi_board.h"
|
||||
#include "wifi_board.h"
|
||||
#include "audio_codecs/box_audio_codec.h"
|
||||
#include "display/st7789_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "LichuangDevBoard"
|
||||
|
||||
|
||||
class Pca9557 : public I2cDevice {
|
||||
public:
|
||||
Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(0x01, 0x03);
|
||||
WriteReg(0x03, 0xf8);
|
||||
}
|
||||
|
||||
void SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint8_t data = ReadReg(0x01);
|
||||
data = (data & ~(1 << bit)) | (level << bit);
|
||||
WriteReg(0x01, data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class LichuangDevBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
i2c_master_dev_handle_t pca9557_handle_;
|
||||
Button boot_button_;
|
||||
St7789Display* display_;
|
||||
Pca9557* pca9557_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@@ -33,39 +54,9 @@ private:
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
void Pca9557ReadRegister(uint8_t addr, uint8_t* data) {
|
||||
uint8_t tmp[1] = {addr};
|
||||
ESP_ERROR_CHECK(i2c_master_transmit_receive(pca9557_handle_, tmp, 1, data, 1, 100));
|
||||
}
|
||||
|
||||
void Pca9557WriteRegister(uint8_t addr, uint8_t data) {
|
||||
uint8_t tmp[2] = {addr, data};
|
||||
ESP_ERROR_CHECK(i2c_master_transmit(pca9557_handle_, tmp, 2, 100));
|
||||
}
|
||||
|
||||
void Pca9557SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint8_t data;
|
||||
Pca9557ReadRegister(0x01, &data);
|
||||
data = (data & ~(1 << bit)) | (level << bit);
|
||||
Pca9557WriteRegister(0x01, data);
|
||||
}
|
||||
|
||||
void InitializePca9557() {
|
||||
i2c_device_config_t pca9557_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = 0x19,
|
||||
.scl_speed_hz = 100000,
|
||||
.scl_wait_us = 0,
|
||||
.flags = {
|
||||
.disable_ack_check = 0,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_, &pca9557_cfg, &pca9557_handle_));
|
||||
assert(pca9557_handle_ != NULL);
|
||||
Pca9557WriteRegister(0x01, 0x03);
|
||||
Pca9557WriteRegister(0x03, 0xf8);
|
||||
// Initialize PCA9557
|
||||
pca9557_ = new Pca9557(i2c_bus_, 0x19);
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
@@ -81,10 +72,48 @@ private:
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetChatState() == kChatStateUnknown && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
// 液晶屏控制IO初始化
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = GPIO_NUM_NC;
|
||||
io_config.dc_gpio_num = GPIO_NUM_39;
|
||||
io_config.spi_mode = 2;
|
||||
io_config.pclk_hz = 80 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 10;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7789
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = GPIO_NUM_NC;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
pca9557_->SetOutputState(0, 0);
|
||||
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
display_ = new St7789Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
}
|
||||
|
||||
public:
|
||||
LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
}
|
||||
@@ -92,8 +121,8 @@ public:
|
||||
virtual void Initialize() override {
|
||||
ESP_LOGI(TAG, "Initializing LichuangDevBoard");
|
||||
InitializeI2c();
|
||||
InitializePca9557();
|
||||
InitializeSpi();
|
||||
InitializeSt7789Display();
|
||||
InitializeButtons();
|
||||
WifiBoard::Initialize();
|
||||
}
|
||||
@@ -115,40 +144,7 @@ public:
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
static St7789Display* display = nullptr;
|
||||
if (display == nullptr) {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
// 液晶屏控制IO初始化
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = GPIO_NUM_NC;
|
||||
io_config.dc_gpio_num = GPIO_NUM_39;
|
||||
io_config.spi_mode = 2;
|
||||
io_config.pclk_hz = 80 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 10;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7789
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = GPIO_NUM_NC;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
Pca9557SetOutputState(0, 0);
|
||||
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, true);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
display = new St7789Display(panel_io, panel, GPIO_NUM_42, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
}
|
||||
return display;
|
||||
return display_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
136
main/display.cc
136
main/display.cc
@@ -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, ¬ification_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);
|
||||
}
|
||||
}
|
||||
@@ -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
192
main/display/display.cc
Normal 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(¬ification_timer_args, ¬ification_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 ¬ification, 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_);
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在聊天状态为空闲时,读取网络状态(避免升级时占用 UART 资源)
|
||||
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
64
main/display/display.h
Normal 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 ¬ification, 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
|
||||
@@ -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,136 @@ 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_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_obj_set_width(status_label_, width_ - 32);
|
||||
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_obj_set_width(notification_label_, width_ - 32);
|
||||
lv_label_set_long_mode(notification_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
lv_label_set_text(notification_label_, "通知");
|
||||
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "st7789_display.h"
|
||||
#include "font_awesome_symbols.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
@@ -9,9 +10,15 @@
|
||||
#define TAG "St7789Display"
|
||||
#define LCD_LEDC_CH LEDC_CHANNEL_0
|
||||
|
||||
St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin,
|
||||
int width, int height, bool mirror_x, bool mirror_y)
|
||||
: panel_io_(panel_io), panel_(panel), mirror_x_(mirror_x), mirror_y_(mirror_y) {
|
||||
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)
|
||||
: panel_io_(panel_io), panel_(panel), backlight_pin_(backlight_pin), backlight_output_invert_(backlight_output_invert),
|
||||
mirror_x_(mirror_x), mirror_y_(mirror_y), swap_xy_(swap_xy) {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
@@ -43,7 +50,7 @@ St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
.vres = static_cast<uint32_t>(height_),
|
||||
.monochrome = false,
|
||||
.rotation = {
|
||||
.swap_xy = true,
|
||||
.swap_xy = swap_xy_,
|
||||
.mirror_x = mirror_x_,
|
||||
.mirror_y = mirror_y_,
|
||||
},
|
||||
@@ -59,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_);
|
||||
}
|
||||
@@ -86,7 +108,7 @@ void St7789Display::InitializeBacklight(gpio_num_t backlight_pin) {
|
||||
.duty = 0,
|
||||
.hpoint = 0,
|
||||
.flags = {
|
||||
.output_invert = true
|
||||
.output_invert = backlight_output_invert_,
|
||||
}
|
||||
};
|
||||
const ledc_timer_config_t backlight_timer = {
|
||||
@@ -103,6 +125,10 @@ void St7789Display::InitializeBacklight(gpio_num_t backlight_pin) {
|
||||
}
|
||||
|
||||
void St7789Display::SetBacklight(uint8_t brightness) {
|
||||
if (backlight_pin_ == GPIO_NUM_NC) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (brightness > 100) {
|
||||
brightness = 100;
|
||||
}
|
||||
@@ -121,3 +147,65 @@ 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_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);
|
||||
}
|
||||
|
||||
@@ -11,18 +11,28 @@ class St7789Display : public Display {
|
||||
private:
|
||||
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
gpio_num_t backlight_pin_ = GPIO_NUM_NC;
|
||||
bool backlight_output_invert_ = false;
|
||||
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;
|
||||
|
||||
public:
|
||||
St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin,
|
||||
int width, int height, bool mirror_x, bool mirror_y);
|
||||
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);
|
||||
~St7789Display();
|
||||
};
|
||||
|
||||
|
||||
7515
main/fonts/GB2312.TXT
Normal file
7515
main/fonts/GB2312.TXT
Normal file
File diff suppressed because it is too large
Load Diff
565
main/fonts/font_awesome_14_1.c
Normal file
565
main/fonts/font_awesome_14_1.c
Normal 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*/
|
||||
|
||||
1233
main/fonts/font_awesome_30_1.c
Normal file
1233
main/fonts/font_awesome_30_1.c
Normal file
File diff suppressed because it is too large
Load Diff
77
main/fonts/font_awesome_symbols.h
Normal file
77
main/fonts/font_awesome_symbols.h
Normal 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
48889
main/fonts/font_puhui_14_1.c
Normal file
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
78/esp-wifi-connect: "~1.3.0"
|
||||
78/esp-wifi-connect: "~1.4.1"
|
||||
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"
|
||||
|
||||
@@ -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 {
|
||||
@@ -169,7 +169,6 @@ void Ota::Upgrade(const std::string& firmware_url) {
|
||||
size_t total_read = 0, recent_read = 0;
|
||||
auto last_calc_time = esp_timer_get_time();
|
||||
while (true) {
|
||||
taskYIELD(); // Avoid watchdog timeout
|
||||
int ret = http->Read(buffer.data(), buffer.size());
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef PROTOCOL_H
|
||||
#define PROTOCOL_H
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
class Protocol {
|
||||
public:
|
||||
virtual ~Protocol() = default;
|
||||
|
||||
virtual void OnIncomingAudio(std::function<void(const std::string& data)> callback) = 0;
|
||||
virtual void OnIncomingJson(std::function<void(const cJSON* root)> callback) = 0;
|
||||
virtual void SendAudio(const std::string& data) = 0;
|
||||
virtual void SendText(const std::string& text) = 0;
|
||||
virtual void SendState(const std::string& state) = 0;
|
||||
virtual void SendAbort() = 0;
|
||||
virtual bool OpenAudioChannel() = 0;
|
||||
virtual void CloseAudioChannel() = 0;
|
||||
virtual void OnAudioChannelOpened(std::function<void()> callback) = 0;
|
||||
virtual void OnAudioChannelClosed(std::function<void()> callback) = 0;
|
||||
virtual bool IsAudioChannelOpened() const = 0;
|
||||
virtual int GetServerSampleRate() const = 0;
|
||||
};
|
||||
|
||||
#endif // PROTOCOL_H
|
||||
|
||||
@@ -72,9 +72,9 @@ bool MqttProtocol::StartMqttClient() {
|
||||
} else if (strcmp(type->valuestring, "goodbye") == 0) {
|
||||
auto session_id = cJSON_GetObjectItem(root, "session_id");
|
||||
if (session_id == nullptr || session_id_ == session_id->valuestring) {
|
||||
if (on_audio_channel_closed_ != nullptr) {
|
||||
on_audio_channel_closed_();
|
||||
}
|
||||
Application::GetInstance().Schedule([this]() {
|
||||
CloseAudioChannel();
|
||||
});
|
||||
}
|
||||
} else if (on_incoming_json_ != nullptr) {
|
||||
on_incoming_json_(root);
|
||||
@@ -85,6 +85,9 @@ bool MqttProtocol::StartMqttClient() {
|
||||
ESP_LOGI(TAG, "Connecting to endpoint %s", endpoint_.c_str());
|
||||
if (!mqtt_->Connect(endpoint_, 8883, client_id_, username_, password_)) {
|
||||
ESP_LOGE(TAG, "Failed to connect to endpoint");
|
||||
if (on_network_error_ != nullptr) {
|
||||
on_network_error_("无法连接服务");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,23 +129,6 @@ void MqttProtocol::SendAudio(const std::string& data) {
|
||||
udp_->Send(encrypted);
|
||||
}
|
||||
|
||||
void MqttProtocol::SendState(const std::string& state) {
|
||||
std::string message = "{";
|
||||
message += "\"session_id\":\"" + session_id_ + "\",";
|
||||
message += "\"type\":\"state\",";
|
||||
message += "\"state\":\"" + state + "\"";
|
||||
message += "}";
|
||||
SendText(message);
|
||||
}
|
||||
|
||||
void MqttProtocol::SendAbort() {
|
||||
std::string message = "{";
|
||||
message += "\"session_id\":\"" + session_id_ + "\",";
|
||||
message += "\"type\":\"abort\"";
|
||||
message += "}";
|
||||
SendText(message);
|
||||
}
|
||||
|
||||
void MqttProtocol::CloseAudioChannel() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(channel_mutex_);
|
||||
@@ -167,7 +153,6 @@ bool MqttProtocol::OpenAudioChannel() {
|
||||
if (mqtt_ == nullptr || !mqtt_->IsConnected()) {
|
||||
ESP_LOGI(TAG, "MQTT is not connected, try to connect now");
|
||||
if (!StartMqttClient()) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MQTT");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -188,6 +173,9 @@ bool MqttProtocol::OpenAudioChannel() {
|
||||
EventBits_t bits = xEventGroupWaitBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & MQTT_PROTOCOL_SERVER_HELLO_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to receive server hello");
|
||||
if (on_network_error_ != nullptr) {
|
||||
on_network_error_("等待响应超时");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -240,22 +228,6 @@ bool MqttProtocol::OpenAudioChannel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MqttProtocol::OnIncomingJson(std::function<void(const cJSON* root)> callback) {
|
||||
on_incoming_json_ = callback;
|
||||
}
|
||||
|
||||
void MqttProtocol::OnIncomingAudio(std::function<void(const std::string& data)> callback) {
|
||||
on_incoming_audio_ = callback;
|
||||
}
|
||||
|
||||
void MqttProtocol::OnAudioChannelOpened(std::function<void()> callback) {
|
||||
on_audio_channel_opened_ = callback;
|
||||
}
|
||||
|
||||
void MqttProtocol::OnAudioChannelClosed(std::function<void()> callback) {
|
||||
on_audio_channel_closed_ = callback;
|
||||
}
|
||||
|
||||
void MqttProtocol::ParseServerHello(const cJSON* root) {
|
||||
auto transport = cJSON_GetObjectItem(root, "transport");
|
||||
if (transport == nullptr || strcmp(transport->valuestring, "udp") != 0) {
|
||||
@@ -297,11 +269,6 @@ void MqttProtocol::ParseServerHello(const cJSON* root) {
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT);
|
||||
}
|
||||
|
||||
int MqttProtocol::GetServerSampleRate() const {
|
||||
return server_sample_rate_;
|
||||
}
|
||||
|
||||
|
||||
static const char hex_chars[] = "0123456789ABCDEF";
|
||||
// 辅助函数,将单个十六进制字符转换为对应的数值
|
||||
static inline uint8_t CharToHex(char c) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#define MQTT_PING_INTERVAL_SECONDS 90
|
||||
#define MQTT_RECONNECT_INTERVAL_MS 10000
|
||||
|
||||
@@ -24,27 +25,14 @@ public:
|
||||
MqttProtocol();
|
||||
~MqttProtocol();
|
||||
|
||||
void OnIncomingAudio(std::function<void(const std::string& data)> callback);
|
||||
void OnIncomingJson(std::function<void(const cJSON* root)> callback);
|
||||
void SendAudio(const std::string& data);
|
||||
void SendText(const std::string& text);
|
||||
void SendState(const std::string& state);
|
||||
void SendAbort();
|
||||
bool OpenAudioChannel();
|
||||
void CloseAudioChannel();
|
||||
void OnAudioChannelOpened(std::function<void()> callback);
|
||||
void OnAudioChannelClosed(std::function<void()> callback);
|
||||
bool IsAudioChannelOpened() const;
|
||||
int GetServerSampleRate() const;
|
||||
void SendAudio(const std::string& data) override;
|
||||
bool OpenAudioChannel() override;
|
||||
void CloseAudioChannel() override;
|
||||
bool IsAudioChannelOpened() const override;
|
||||
|
||||
private:
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
|
||||
std::function<void(const cJSON* root)> on_incoming_json_;
|
||||
std::function<void(const std::string& data)> on_incoming_audio_;
|
||||
std::function<void()> on_audio_channel_opened_;
|
||||
std::function<void()> on_audio_channel_closed_;
|
||||
|
||||
std::string endpoint_;
|
||||
std::string client_id_;
|
||||
std::string username_;
|
||||
@@ -61,12 +49,12 @@ private:
|
||||
int udp_port_;
|
||||
uint32_t local_sequence_;
|
||||
uint32_t remote_sequence_;
|
||||
std::string session_id_;
|
||||
int server_sample_rate_ = 16000;
|
||||
|
||||
bool StartMqttClient();
|
||||
void ParseServerHello(const cJSON* root);
|
||||
std::string DecodeHexString(const std::string& hex_string);
|
||||
|
||||
void SendText(const std::string& text) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
59
main/protocols/protocol.cc
Normal file
59
main/protocols/protocol.cc
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "protocol.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Protocol"
|
||||
|
||||
void Protocol::OnIncomingJson(std::function<void(const cJSON* root)> callback) {
|
||||
on_incoming_json_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::OnIncomingAudio(std::function<void(const std::string& data)> callback) {
|
||||
on_incoming_audio_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::OnAudioChannelOpened(std::function<void()> callback) {
|
||||
on_audio_channel_opened_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::OnAudioChannelClosed(std::function<void()> callback) {
|
||||
on_audio_channel_closed_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::OnNetworkError(std::function<void(const std::string& message)> callback) {
|
||||
on_network_error_ = callback;
|
||||
}
|
||||
|
||||
void Protocol::SendAbortSpeaking(AbortReason reason) {
|
||||
std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"abort\"";
|
||||
if (reason == kAbortReasonWakeWordDetected) {
|
||||
message += ",\"reason\":\"wake_word_detected\"";
|
||||
}
|
||||
message += "}";
|
||||
SendText(message);
|
||||
}
|
||||
|
||||
void Protocol::SendWakeWordDetected(const std::string& wake_word) {
|
||||
std::string json = "{\"session_id\":\"" + session_id_ +
|
||||
"\",\"type\":\"listen\",\"state\":\"detect\",\"text\":\"" + wake_word + "\"}";
|
||||
SendText(json);
|
||||
}
|
||||
|
||||
void Protocol::SendStartListening(ListeningMode mode) {
|
||||
std::string message = "{\"session_id\":\"" + session_id_ + "\"";
|
||||
message += ",\"type\":\"listen\",\"state\":\"start\"";
|
||||
if (mode == kListeningModeAlwaysOn) {
|
||||
message += ",\"mode\":\"realtime\"";
|
||||
} else if (mode == kListeningModeAutoStop) {
|
||||
message += ",\"mode\":\"auto\"";
|
||||
} else {
|
||||
message += ",\"mode\":\"manual\"";
|
||||
}
|
||||
message += "}";
|
||||
SendText(message);
|
||||
}
|
||||
|
||||
void Protocol::SendStopListening() {
|
||||
std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"listen\",\"state\":\"stop\"}";
|
||||
SendText(message);
|
||||
}
|
||||
63
main/protocols/protocol.h
Normal file
63
main/protocols/protocol.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef PROTOCOL_H
|
||||
#define PROTOCOL_H
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
struct BinaryProtocol3 {
|
||||
uint8_t type;
|
||||
uint8_t reserved;
|
||||
uint16_t payload_size;
|
||||
uint8_t payload[];
|
||||
} __attribute__((packed));
|
||||
|
||||
enum AbortReason {
|
||||
kAbortReasonNone,
|
||||
kAbortReasonWakeWordDetected
|
||||
};
|
||||
|
||||
enum ListeningMode {
|
||||
kListeningModeAutoStop,
|
||||
kListeningModeManualStop,
|
||||
kListeningModeAlwaysOn // 需要 AEC 支持
|
||||
};
|
||||
|
||||
class Protocol {
|
||||
public:
|
||||
virtual ~Protocol() = default;
|
||||
|
||||
inline int server_sample_rate() const {
|
||||
return server_sample_rate_;
|
||||
}
|
||||
|
||||
void OnIncomingAudio(std::function<void(const std::string& data)> callback);
|
||||
void OnIncomingJson(std::function<void(const cJSON* root)> callback);
|
||||
void OnAudioChannelOpened(std::function<void()> callback);
|
||||
void OnAudioChannelClosed(std::function<void()> callback);
|
||||
void OnNetworkError(std::function<void(const std::string& message)> callback);
|
||||
|
||||
virtual bool OpenAudioChannel() = 0;
|
||||
virtual void CloseAudioChannel() = 0;
|
||||
virtual bool IsAudioChannelOpened() const = 0;
|
||||
virtual void SendAudio(const std::string& data) = 0;
|
||||
virtual void SendWakeWordDetected(const std::string& wake_word);
|
||||
virtual void SendStartListening(ListeningMode mode);
|
||||
virtual void SendStopListening();
|
||||
virtual void SendAbortSpeaking(AbortReason reason);
|
||||
|
||||
protected:
|
||||
std::function<void(const cJSON* root)> on_incoming_json_;
|
||||
std::function<void(const std::string& data)> on_incoming_audio_;
|
||||
std::function<void()> on_audio_channel_opened_;
|
||||
std::function<void()> on_audio_channel_closed_;
|
||||
std::function<void(const std::string& message)> on_network_error_;
|
||||
|
||||
int server_sample_rate_ = 16000;
|
||||
std::string session_id_;
|
||||
|
||||
virtual void SendText(const std::string& text) = 0;
|
||||
};
|
||||
|
||||
#endif // PROTOCOL_H
|
||||
|
||||
150
main/protocols/websocket_protocol.cc
Normal file
150
main/protocols/websocket_protocol.cc
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "websocket_protocol.h"
|
||||
#include "board.h"
|
||||
#include "system_info.h"
|
||||
#include "application.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cJSON.h>
|
||||
#include <esp_log.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define TAG "WS"
|
||||
|
||||
#ifdef CONFIG_CONNECTION_TYPE_WEBSOCKET
|
||||
|
||||
WebsocketProtocol::WebsocketProtocol() {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
WebsocketProtocol::~WebsocketProtocol() {
|
||||
if (websocket_ != nullptr) {
|
||||
delete websocket_;
|
||||
}
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
void WebsocketProtocol::SendAudio(const std::string& data) {
|
||||
if (websocket_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
websocket_->Send(data.data(), data.size(), true);
|
||||
}
|
||||
|
||||
void WebsocketProtocol::SendText(const std::string& text) {
|
||||
if (websocket_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
websocket_->Send(text);
|
||||
}
|
||||
|
||||
bool WebsocketProtocol::IsAudioChannelOpened() const {
|
||||
return websocket_ != nullptr;
|
||||
}
|
||||
|
||||
void WebsocketProtocol::CloseAudioChannel() {
|
||||
if (websocket_ != nullptr) {
|
||||
delete websocket_;
|
||||
websocket_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool WebsocketProtocol::OpenAudioChannel() {
|
||||
if (websocket_ != nullptr) {
|
||||
delete websocket_;
|
||||
}
|
||||
|
||||
std::string url = CONFIG_WEBSOCKET_URL;
|
||||
std::string token = "Bearer " + std::string(CONFIG_WEBSOCKET_ACCESS_TOKEN);
|
||||
websocket_ = Board::GetInstance().CreateWebSocket();
|
||||
websocket_->SetHeader("Authorization", token.c_str());
|
||||
websocket_->SetHeader("Protocol-Version", "1");
|
||||
websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
|
||||
|
||||
websocket_->OnData([this](const char* data, size_t len, bool binary) {
|
||||
if (binary) {
|
||||
if (on_incoming_audio_ != nullptr) {
|
||||
on_incoming_audio_(std::string(data, len));
|
||||
}
|
||||
} else {
|
||||
// Parse JSON data
|
||||
auto root = cJSON_Parse(data);
|
||||
auto type = cJSON_GetObjectItem(root, "type");
|
||||
if (type != NULL) {
|
||||
if (strcmp(type->valuestring, "hello") == 0) {
|
||||
ParseServerHello(root);
|
||||
} else {
|
||||
if (on_incoming_json_ != nullptr) {
|
||||
on_incoming_json_(root);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Missing message type, data: %s", data);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
});
|
||||
|
||||
websocket_->OnDisconnected([this]() {
|
||||
ESP_LOGI(TAG, "Websocket disconnected");
|
||||
if (on_audio_channel_closed_ != nullptr) {
|
||||
on_audio_channel_closed_();
|
||||
}
|
||||
});
|
||||
|
||||
if (!websocket_->Connect(url.c_str())) {
|
||||
ESP_LOGE(TAG, "Failed to connect to websocket server");
|
||||
if (on_network_error_ != nullptr) {
|
||||
on_network_error_("无法连接服务");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send hello message to describe the client
|
||||
// keys: message type, version, audio_params (format, sample_rate, channels)
|
||||
std::string message = "{";
|
||||
message += "\"type\":\"hello\",";
|
||||
message += "\"version\": 1,";
|
||||
message += "\"transport\":\"websocket\",";
|
||||
message += "\"audio_params\":{";
|
||||
message += "\"format\":\"opus\", \"sample_rate\":16000, \"channels\":1, \"frame_duration\":" + std::to_string(OPUS_FRAME_DURATION_MS);
|
||||
message += "}}";
|
||||
websocket_->Send(message);
|
||||
|
||||
// Wait for server hello
|
||||
EventBits_t bits = xEventGroupWaitBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to receive server hello");
|
||||
if (on_network_error_ != nullptr) {
|
||||
on_network_error_("等待响应超时");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (on_audio_channel_opened_ != nullptr) {
|
||||
on_audio_channel_opened_();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebsocketProtocol::ParseServerHello(const cJSON* root) {
|
||||
auto transport = cJSON_GetObjectItem(root, "transport");
|
||||
if (transport == nullptr || strcmp(transport->valuestring, "websocket") != 0) {
|
||||
ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring);
|
||||
return;
|
||||
}
|
||||
|
||||
auto audio_params = cJSON_GetObjectItem(root, "audio_params");
|
||||
if (audio_params != NULL) {
|
||||
auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate");
|
||||
if (sample_rate != NULL) {
|
||||
server_sample_rate_ = sample_rate->valueint;
|
||||
}
|
||||
}
|
||||
|
||||
xEventGroupSetBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT);
|
||||
}
|
||||
|
||||
#endif
|
||||
31
main/protocols/websocket_protocol.h
Normal file
31
main/protocols/websocket_protocol.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef _WEBSOCKET_PROTOCOL_H_
|
||||
#define _WEBSOCKET_PROTOCOL_H_
|
||||
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
#include <web_socket.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT (1 << 0)
|
||||
|
||||
class WebsocketProtocol : public Protocol {
|
||||
public:
|
||||
WebsocketProtocol();
|
||||
~WebsocketProtocol();
|
||||
|
||||
void SendAudio(const std::string& data) override;
|
||||
bool OpenAudioChannel() override;
|
||||
void CloseAudioChannel() override;
|
||||
bool IsAudioChannelOpened() const override;
|
||||
|
||||
private:
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
WebSocket* websocket_ = nullptr;
|
||||
|
||||
void ParseServerHello(const cJSON* root);
|
||||
void SendText(const std::string& text) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -11,7 +11,7 @@ Settings::Settings(const std::string& ns, bool read_write) : ns_(ns), read_write
|
||||
|
||||
Settings::~Settings() {
|
||||
if (nvs_handle_ != 0) {
|
||||
if (read_write_) {
|
||||
if (read_write_ && dirty_) {
|
||||
ESP_ERROR_CHECK(nvs_commit(nvs_handle_));
|
||||
}
|
||||
nvs_close(nvs_handle_);
|
||||
@@ -37,6 +37,7 @@ std::string Settings::GetString(const std::string& key, const std::string& defau
|
||||
void Settings::SetString(const std::string& key, const std::string& value) {
|
||||
if (read_write_) {
|
||||
ESP_ERROR_CHECK(nvs_set_str(nvs_handle_, key.c_str(), value.c_str()));
|
||||
dirty_ = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
|
||||
}
|
||||
@@ -57,6 +58,23 @@ int32_t Settings::GetInt(const std::string& key, int32_t default_value) {
|
||||
void Settings::SetInt(const std::string& key, int32_t value) {
|
||||
if (read_write_) {
|
||||
ESP_ERROR_CHECK(nvs_set_i32(nvs_handle_, key.c_str(), value));
|
||||
dirty_ = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::EraseKey(const std::string& key) {
|
||||
if (read_write_) {
|
||||
ESP_ERROR_CHECK(nvs_erase_key(nvs_handle_, key.c_str()));
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::EraseAll() {
|
||||
if (read_write_) {
|
||||
ESP_ERROR_CHECK(nvs_erase_all(nvs_handle_));
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ public:
|
||||
void SetString(const std::string& key, const std::string& value);
|
||||
int32_t GetInt(const std::string& key, int32_t default_value = 0);
|
||||
void SetInt(const std::string& key, int32_t value);
|
||||
void EraseKey(const std::string& key);
|
||||
void EraseAll();
|
||||
|
||||
private:
|
||||
std::string ns_;
|
||||
nvs_handle_t nvs_handle_ = 0;
|
||||
bool read_write_ = false;
|
||||
bool dirty_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#ifndef _SYSTEM_RESET_H
|
||||
#define _SYSTEM_RESET_H
|
||||
|
||||
class SystemReset {
|
||||
public:
|
||||
static SystemReset& GetInstance() {
|
||||
static SystemReset instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void CheckButtons();
|
||||
|
||||
private:
|
||||
SystemReset(); // 构造函数私有化
|
||||
SystemReset(const SystemReset&) = delete; // 禁用拷贝构造
|
||||
SystemReset& operator=(const SystemReset&) = delete; // 禁用赋值操作
|
||||
|
||||
void ResetNvsFlash();
|
||||
void ResetToFactory();
|
||||
void RestartInSeconds(int seconds);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -4,9 +4,9 @@
|
||||
#include <esp_log.h>
|
||||
#include <model_path.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sstream>
|
||||
|
||||
#define DETECTION_RUNNING_EVENT 1
|
||||
#define WAKE_WORD_ENCODED_EVENT 2
|
||||
|
||||
static const char* TAG = "WakeWordDetect";
|
||||
|
||||
@@ -24,10 +24,7 @@ WakeWordDetect::~WakeWordDetect() {
|
||||
}
|
||||
|
||||
if (wake_word_encode_task_stack_ != nullptr) {
|
||||
free(wake_word_encode_task_stack_);
|
||||
}
|
||||
if (audio_detection_task_stack_ != nullptr) {
|
||||
heap_caps_free(audio_detection_task_stack_);
|
||||
heap_caps_free(wake_word_encode_task_stack_);
|
||||
}
|
||||
|
||||
vEventGroupDelete(event_group_);
|
||||
@@ -43,6 +40,13 @@ void WakeWordDetect::Initialize(int channels, bool reference) {
|
||||
ESP_LOGI(TAG, "Model %d: %s", i, models->model_name[i]);
|
||||
if (strstr(models->model_name[i], ESP_WN_PREFIX) != NULL) {
|
||||
wakenet_model_ = models->model_name[i];
|
||||
auto words = esp_srmodel_get_wake_words(models, wakenet_model_);
|
||||
// split by ";" to get all wake words
|
||||
std::stringstream ss(words);
|
||||
std::string word;
|
||||
while (std::getline(ss, word, ';')) {
|
||||
wake_words_.push_back(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,16 +84,14 @@ void WakeWordDetect::Initialize(int channels, bool reference) {
|
||||
|
||||
afe_detection_data_ = esp_afe_sr_v1.create_from_config(&afe_config);
|
||||
|
||||
const size_t audio_detection_task_stack_size = 4096 * 2;
|
||||
audio_detection_task_stack_ = (StackType_t*)heap_caps_malloc(audio_detection_task_stack_size, MALLOC_CAP_SPIRAM);
|
||||
xTaskCreateStatic([](void* arg) {
|
||||
xTaskCreate([](void* arg) {
|
||||
auto this_ = (WakeWordDetect*)arg;
|
||||
this_->AudioDetectionTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "audio_detection", audio_detection_task_stack_size, this, 1, audio_detection_task_stack_, &audio_detection_task_buffer_);
|
||||
}, "audio_detection", 4096 * 2, this, 1, nullptr);
|
||||
}
|
||||
|
||||
void WakeWordDetect::OnWakeWordDetected(std::function<void()> callback) {
|
||||
void WakeWordDetect::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
|
||||
wake_word_detected_callback_ = callback;
|
||||
}
|
||||
|
||||
@@ -149,11 +151,11 @@ void WakeWordDetect::AudioDetectionTask() {
|
||||
}
|
||||
|
||||
if (res->wakeup_state == WAKENET_DETECTED) {
|
||||
ESP_LOGI(TAG, "Wake word detected");
|
||||
StopDetection();
|
||||
last_detected_wake_word_ = wake_words_[res->wake_word_index - 1];
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_();
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,10 +172,9 @@ void WakeWordDetect::StoreWakeWordData(uint16_t* data, size_t samples) {
|
||||
}
|
||||
|
||||
void WakeWordDetect::EncodeWakeWordData() {
|
||||
xEventGroupClearBits(event_group_, WAKE_WORD_ENCODED_EVENT);
|
||||
wake_word_opus_.clear();
|
||||
if (wake_word_encode_task_stack_ == nullptr) {
|
||||
wake_word_encode_task_stack_ = (StackType_t*)malloc(4096 * 8);
|
||||
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(4096 * 8, MALLOC_CAP_SPIRAM);
|
||||
}
|
||||
wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
|
||||
auto this_ = (WakeWordDetect*)arg;
|
||||
@@ -187,15 +188,18 @@ void WakeWordDetect::EncodeWakeWordData() {
|
||||
encoder->Encode(pcm, [this_](const uint8_t* opus, size_t opus_size) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(std::string(reinterpret_cast<const char*>(opus), opus_size));
|
||||
this_->wake_word_cv_.notify_one();
|
||||
this_->wake_word_cv_.notify_all();
|
||||
});
|
||||
}
|
||||
this_->wake_word_pcm_.clear();
|
||||
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Encode wake word opus %zu packets in %lld ms", this_->wake_word_opus_.size(), (end_time - start_time) / 1000);
|
||||
xEventGroupSetBits(this_->event_group_, WAKE_WORD_ENCODED_EVENT);
|
||||
this_->wake_word_cv_.notify_one();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.push_back("");
|
||||
this_->wake_word_cv_.notify_all();
|
||||
}
|
||||
delete encoder;
|
||||
vTaskDelete(NULL);
|
||||
}, "encode_detect_packets", 4096 * 8, this, 1, wake_word_encode_task_stack_, &wake_word_encode_task_buffer_);
|
||||
@@ -204,12 +208,9 @@ void WakeWordDetect::EncodeWakeWordData() {
|
||||
bool WakeWordDetect::GetWakeWordOpus(std::string& opus) {
|
||||
std::unique_lock<std::mutex> lock(wake_word_mutex_);
|
||||
wake_word_cv_.wait(lock, [this]() {
|
||||
return !wake_word_opus_.empty() || (xEventGroupGetBits(event_group_) & WAKE_WORD_ENCODED_EVENT);
|
||||
return !wake_word_opus_.empty();
|
||||
});
|
||||
if (wake_word_opus_.empty()) {
|
||||
return false;
|
||||
}
|
||||
opus.swap(wake_word_opus_.front());
|
||||
wake_word_opus_.pop_front();
|
||||
return true;
|
||||
return !opus.empty();
|
||||
}
|
||||
|
||||
@@ -23,28 +23,27 @@ public:
|
||||
|
||||
void Initialize(int channels, bool reference);
|
||||
void Feed(std::vector<int16_t>& data);
|
||||
void OnWakeWordDetected(std::function<void()> callback);
|
||||
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
|
||||
void OnVadStateChange(std::function<void(bool speaking)> callback);
|
||||
void StartDetection();
|
||||
void StopDetection();
|
||||
bool IsDetectionRunning();
|
||||
void EncodeWakeWordData();
|
||||
bool GetWakeWordOpus(std::string& opus);
|
||||
const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
|
||||
|
||||
private:
|
||||
esp_afe_sr_data_t* afe_detection_data_ = nullptr;
|
||||
char* wakenet_model_ = NULL;
|
||||
std::vector<std::string> wake_words_;
|
||||
std::vector<int16_t> input_buffer_;
|
||||
EventGroupHandle_t event_group_;
|
||||
std::function<void()> wake_word_detected_callback_;
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
std::function<void(bool speaking)> vad_state_change_callback_;
|
||||
bool is_speaking_ = false;
|
||||
int channels_;
|
||||
bool reference_;
|
||||
|
||||
TaskHandle_t audio_detection_task_ = nullptr;
|
||||
StaticTask_t audio_detection_task_buffer_;
|
||||
StackType_t* audio_detection_task_stack_ = nullptr;
|
||||
std::string last_detected_wake_word_;
|
||||
|
||||
TaskHandle_t wake_word_encode_task_ = nullptr;
|
||||
StaticTask_t wake_word_encode_task_buffer_;
|
||||
|
||||
@@ -14,6 +14,7 @@ CONFIG_USE_WAKENET=y
|
||||
CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
|
||||
CONFIG_USE_MULTINET=n
|
||||
|
||||
ESP_TASK_WDT_TIMEOUT_S=10
|
||||
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
|
||||
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ def get_board_name(folder):
|
||||
return "bread-compact-wifi"
|
||||
elif "KevinBox1" in basename:
|
||||
return "kevin-box-1"
|
||||
if basename.startswith("v0.7"):
|
||||
if basename.startswith("v0.7") or basename.startswith("v0.8") or basename.startswith("v0.9"):
|
||||
return basename.split("_")[1]
|
||||
raise Exception(f"Unknown board name: {basename}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user