加入中文UI

This commit is contained in:
Terrence
2024-11-18 06:17:39 +08:00
parent 794e6f4bef
commit 6bfe2719a8
39 changed files with 58931 additions and 237 deletions

View File

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

View File

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

104
main/boards/common/board.cc Normal file
View File

@ -0,0 +1,104 @@
#include "board.h"
#include "system_info.h"
#include <esp_log.h>
#include <esp_ota_ops.h>
#include <esp_chip_info.h>
// static const char *TAG = "Board";
bool Board::GetBatteryLevel(int &level, bool& charging) {
return false;
}
std::string Board::GetJson() {
/*
{
"flash_size": 4194304,
"psram_size": 0,
"minimum_free_heap_size": 123456,
"mac_address": "00:00:00:00:00:00",
"chip_model_name": "esp32s3",
"chip_info": {
"model": 1,
"cores": 2,
"revision": 0,
"features": 0
},
"application": {
"name": "my-app",
"version": "1.0.0",
"compile_time": "2021-01-01T00:00:00Z"
"idf_version": "4.2-dev"
"elf_sha256": ""
},
"partition_table": [
"app": {
"label": "app",
"type": 1,
"subtype": 2,
"address": 0x10000,
"size": 0x100000
}
],
"ota": {
"label": "ota_0"
}
}
*/
std::string json = "{";
json += "\"flash_size\":" + std::to_string(SystemInfo::GetFlashSize()) + ",";
json += "\"minimum_free_heap_size\":" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + ",";
json += "\"mac_address\":\"" + SystemInfo::GetMacAddress() + "\",";
json += "\"chip_model_name\":\"" + SystemInfo::GetChipModelName() + "\",";
json += "\"chip_info\":{";
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
json += "\"model\":" + std::to_string(chip_info.model) + ",";
json += "\"cores\":" + std::to_string(chip_info.cores) + ",";
json += "\"revision\":" + std::to_string(chip_info.revision) + ",";
json += "\"features\":" + std::to_string(chip_info.features);
json += "},";
json += "\"application\":{";
auto app_desc = esp_app_get_description();
json += "\"name\":\"" + std::string(app_desc->project_name) + "\",";
json += "\"version\":\"" + std::string(app_desc->version) + "\",";
json += "\"compile_time\":\"" + std::string(app_desc->date) + "T" + std::string(app_desc->time) + "Z\",";
json += "\"idf_version\":\"" + std::string(app_desc->idf_ver) + "\",";
char sha256_str[65];
for (int i = 0; i < 32; i++) {
snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
}
json += "\"elf_sha256\":\"" + std::string(sha256_str) + "\"";
json += "},";
json += "\"partition_table\": [";
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
while (it) {
const esp_partition_t *partition = esp_partition_get(it);
json += "{";
json += "\"label\":\"" + std::string(partition->label) + "\",";
json += "\"type\":" + std::to_string(partition->type) + ",";
json += "\"subtype\":" + std::to_string(partition->subtype) + ",";
json += "\"address\":" + std::to_string(partition->address) + ",";
json += "\"size\":" + std::to_string(partition->size);
json += "},";
it = esp_partition_next(it);
}
json.pop_back(); // Remove the last comma
json += "],";
json += "\"ota\":{";
auto ota_partition = esp_ota_get_running_partition();
json += "\"label\":\"" + std::string(ota_partition->label) + "\"";
json += "},";
json += "\"board\":" + GetBoardJson();
// Close the JSON object
json += "}";
return json;
}

View File

@ -0,0 +1,55 @@
#ifndef BOARD_H
#define BOARD_H
#include <http.h>
#include <web_socket.h>
#include <mqtt.h>
#include <udp.h>
#include <string>
#include "led.h"
void* create_board();
class AudioCodec;
class Display;
class Board {
private:
Board(const Board&) = delete; // 禁用拷贝构造函数
Board& operator=(const Board&) = delete; // 禁用赋值操作
virtual std::string GetBoardJson() = 0;
protected:
Board() = default;
public:
static Board& GetInstance() {
static Board* instance = nullptr;
if (nullptr == instance) {
instance = static_cast<Board*>(create_board());
}
return *instance;
}
virtual void Initialize() = 0;
virtual void StartNetwork() = 0;
virtual ~Board() = default;
virtual Led* GetBuiltinLed() = 0;
virtual AudioCodec* GetAudioCodec() = 0;
virtual Display* GetDisplay() = 0;
virtual Http* CreateHttp() = 0;
virtual WebSocket* CreateWebSocket() = 0;
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) \
void* create_board() { \
return new BOARD_CLASS_NAME(); \
}
#endif // BOARD_H

View File

@ -0,0 +1,83 @@
#include "button.h"
#include <esp_log.h>
static const char* TAG = "Button";
Button::Button(gpio_num_t gpio_num) : gpio_num_(gpio_num) {
if (gpio_num == GPIO_NUM_NC) {
return;
}
button_config_t button_config = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = 1000,
.short_press_time = 50,
.gpio_button_config = {
.gpio_num = gpio_num,
.active_level = 0
}
};
button_handle_ = iot_button_create(&button_config);
if (button_handle_ == NULL) {
ESP_LOGE(TAG, "Failed to create button handle");
return;
}
}
Button::~Button() {
if (button_handle_ != NULL) {
iot_button_delete(button_handle_);
}
}
void Button::OnPress(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_press_ = 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_();
}
}, this);
}
void Button::OnLongPress(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_long_press_ = callback;
iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_long_press_) {
button->on_long_press_();
}
}, this);
}
void Button::OnClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_click_) {
button->on_click_();
}
}, this);
}
void Button::OnDoubleClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_double_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_double_click_) {
button->on_double_click_();
}
}, this);
}

View File

@ -0,0 +1,28 @@
#ifndef BUTTON_H_
#define BUTTON_H_
#include <driver/gpio.h>
#include <iot_button.h>
#include <functional>
class Button {
public:
Button(gpio_num_t gpio_num);
~Button();
void OnPress(std::function<void()> callback);
void OnLongPress(std::function<void()> callback);
void OnClick(std::function<void()> callback);
void OnDoubleClick(std::function<void()> callback);
private:
gpio_num_t gpio_num_;
button_handle_t button_handle_;
std::function<void()> on_press_;
std::function<void()> on_long_press_;
std::function<void()> on_click_;
std::function<void()> on_double_click_;
};
#endif // BUTTON_H_

131
main/boards/common/led.cc Normal file
View File

@ -0,0 +1,131 @@
#include "led.h"
#include "board.h"
#include <cstring>
#include <esp_log.h>
#define TAG "Led"
Led::Led(gpio_num_t gpio) {
mutex_ = xSemaphoreCreateMutex();
blink_event_group_ = xEventGroupCreate();
xEventGroupSetBits(blink_event_group_, BLINK_TASK_STOPPED_BIT);
if (gpio == GPIO_NUM_NC) {
ESP_LOGI(TAG, "Builtin LED not connected");
return;
}
led_strip_config_t strip_config = {};
strip_config.strip_gpio_num = gpio;
strip_config.max_leds = 1;
strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB;
strip_config.led_model = LED_MODEL_WS2812;
led_strip_rmt_config_t rmt_config = {};
rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_));
led_strip_clear(led_strip_);
SetGrey();
}
Led::~Led() {
StopBlinkInternal();
if (led_strip_ != nullptr) {
led_strip_del(led_strip_);
}
if (mutex_ != nullptr) {
vSemaphoreDelete(mutex_);
}
if (blink_event_group_ != nullptr) {
vEventGroupDelete(blink_event_group_);
}
}
void Led::SetColor(uint8_t r, uint8_t g, uint8_t b) {
r_ = r;
g_ = g;
b_ = b;
}
void Led::TurnOn() {
if (led_strip_ == nullptr) {
return;
}
StopBlinkInternal();
xSemaphoreTake(mutex_, portMAX_DELAY);
led_strip_set_pixel(led_strip_, 0, r_, g_, b_);
led_strip_refresh(led_strip_);
xSemaphoreGive(mutex_);
}
void Led::TurnOff() {
if (led_strip_ == nullptr) {
return;
}
StopBlinkInternal();
xSemaphoreTake(mutex_, portMAX_DELAY);
led_strip_clear(led_strip_);
xSemaphoreGive(mutex_);
}
void Led::BlinkOnce() {
Blink(1, 100);
}
void Led::Blink(int times, int interval_ms) {
StartBlinkTask(times, interval_ms);
}
void Led::StartContinuousBlink(int interval_ms) {
StartBlinkTask(BLINK_INFINITE, interval_ms);
}
void Led::StartBlinkTask(int times, int interval_ms) {
if (led_strip_ == nullptr) {
return;
}
StopBlinkInternal();
xSemaphoreTake(mutex_, portMAX_DELAY);
blink_times_ = times;
blink_interval_ms_ = interval_ms;
should_blink_ = true;
xEventGroupClearBits(blink_event_group_, BLINK_TASK_STOPPED_BIT);
xEventGroupSetBits(blink_event_group_, BLINK_TASK_RUNNING_BIT);
xTaskCreate([](void* obj) {
auto this_ = static_cast<Led*>(obj);
int count = 0;
while (this_->should_blink_ && (this_->blink_times_ == BLINK_INFINITE || count < this_->blink_times_)) {
xSemaphoreTake(this_->mutex_, portMAX_DELAY);
led_strip_set_pixel(this_->led_strip_, 0, this_->r_, this_->g_, this_->b_);
led_strip_refresh(this_->led_strip_);
xSemaphoreGive(this_->mutex_);
vTaskDelay(this_->blink_interval_ms_ / portTICK_PERIOD_MS);
if (!this_->should_blink_) break;
xSemaphoreTake(this_->mutex_, portMAX_DELAY);
led_strip_clear(this_->led_strip_);
xSemaphoreGive(this_->mutex_);
vTaskDelay(this_->blink_interval_ms_ / portTICK_PERIOD_MS);
if (this_->blink_times_ != BLINK_INFINITE) count++;
}
this_->blink_task_ = nullptr;
xEventGroupClearBits(this_->blink_event_group_, BLINK_TASK_RUNNING_BIT);
xEventGroupSetBits(this_->blink_event_group_, BLINK_TASK_STOPPED_BIT);
vTaskDelete(NULL);
}, "blink", 2048, this, tskIDLE_PRIORITY, &blink_task_);
xSemaphoreGive(mutex_);
}
void Led::StopBlinkInternal() {
should_blink_ = false;
xEventGroupWaitBits(blink_event_group_, BLINK_TASK_STOPPED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
}

49
main/boards/common/led.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef _LED_H_
#define _LED_H_
#include <led_strip.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <atomic>
#define BLINK_INFINITE -1
#define BLINK_TASK_STOPPED_BIT BIT0
#define BLINK_TASK_RUNNING_BIT BIT1
#define DEFAULT_BRIGHTNESS 4
#define HIGH_BRIGHTNESS 16
#define LOW_BRIGHTNESS 2
class Led {
public:
Led(gpio_num_t gpio);
~Led();
void BlinkOnce();
void Blink(int times, int interval_ms);
void StartContinuousBlink(int interval_ms);
void TurnOn();
void TurnOff();
void SetColor(uint8_t r, uint8_t g, uint8_t b);
void SetWhite(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, brightness, brightness); }
void SetGrey(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, brightness, brightness); }
void SetRed(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(brightness, 0, 0); }
void SetGreen(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(0, brightness, 0); }
void SetBlue(uint8_t brightness = DEFAULT_BRIGHTNESS) { SetColor(0, 0, brightness); }
private:
SemaphoreHandle_t mutex_;
EventGroupHandle_t blink_event_group_;
TaskHandle_t blink_task_ = nullptr;
led_strip_handle_t led_strip_ = nullptr;
uint8_t r_ = 0, g_ = 0, b_ = 0;
int blink_times_ = 0;
int blink_interval_ms_ = 0;
std::atomic<bool> should_blink_{false};
void StartBlinkTask(int times, int interval_ms);
void StopBlinkInternal();
};
#endif // _LED_H_

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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