mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-01-14 01:07:30 +08:00
add monitoring voltage of battery
This commit is contained in:
parent
e2f9911053
commit
109398a655
@ -740,6 +740,33 @@ menu "Camera Configuration"
|
||||
endif
|
||||
endmenu
|
||||
|
||||
menu "Battery Configuration"
|
||||
config BATTERY_ADC_GPIO
|
||||
int "Battery ADC GPIO"
|
||||
default 14
|
||||
help
|
||||
GPIO pin for battery voltage reading.
|
||||
|
||||
config BATTERY_VOLTAGE_DIVIDER_FACTOR
|
||||
string "Voltage Divider Factor"
|
||||
default "2.0"
|
||||
help
|
||||
Voltage divider factor (R1+R2)/R2.
|
||||
For two 100k resistors, the factor is 2.0.
|
||||
|
||||
config BATTERY_MAX_VOLTAGE
|
||||
int "Max Battery Voltage (mV)"
|
||||
default 4200
|
||||
help
|
||||
Maximum battery voltage in mV (100%).
|
||||
|
||||
config BATTERY_MIN_VOLTAGE
|
||||
int "Min Battery Voltage (mV)"
|
||||
default 3300
|
||||
help
|
||||
Minimum battery voltage in mV (0%).
|
||||
endmenu
|
||||
|
||||
menu "TAIJIPAI_S3_CONFIG"
|
||||
depends on BOARD_TYPE_TAIJI_PI_S3
|
||||
choice I2S_TYPE_TAIJIPI_S3
|
||||
|
||||
@ -10,6 +10,9 @@
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_chip_info.h>
|
||||
#include <esp_random.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
#include <esp_adc/adc_cali.h>
|
||||
#include <esp_adc/adc_cali_scheme.h>
|
||||
|
||||
#define TAG "Board"
|
||||
|
||||
@ -58,6 +61,89 @@ std::string Board::GenerateUuid()
|
||||
|
||||
bool Board::GetBatteryLevel(int &level, bool &charging, bool &discharging)
|
||||
{
|
||||
#ifdef CONFIG_BATTERY_ADC_GPIO
|
||||
static adc_oneshot_unit_handle_t adc_handle = nullptr;
|
||||
static adc_cali_handle_t adc_cali_handle = nullptr;
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle));
|
||||
|
||||
adc_oneshot_chan_cfg_t config = {
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
};
|
||||
|
||||
adc_channel_t channel;
|
||||
adc_unit_t unit_id = ADC_UNIT_1;
|
||||
if (adc_oneshot_io_to_channel(CONFIG_BATTERY_ADC_GPIO, &unit_id, &channel) == ESP_OK)
|
||||
{
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, channel, &config));
|
||||
|
||||
adc_cali_curve_fitting_config_t cali_config = {
|
||||
.unit_id = unit_id,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
if (adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle) != ESP_OK)
|
||||
{
|
||||
adc_cali_line_fitting_config_t line_config = {
|
||||
.unit_id = unit_id,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
adc_cali_create_scheme_line_fitting(&line_config, &adc_cali_handle);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "ADC channel not found for GPIO %d", CONFIG_BATTERY_ADC_GPIO);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized)
|
||||
return false;
|
||||
|
||||
int raw = 0;
|
||||
adc_channel_t channel;
|
||||
adc_unit_t unit_id = ADC_UNIT_1;
|
||||
adc_oneshot_io_to_channel(CONFIG_BATTERY_ADC_GPIO, &unit_id, &channel);
|
||||
|
||||
if (adc_oneshot_read(adc_handle, channel, &raw) == ESP_OK)
|
||||
{
|
||||
int voltage = 0;
|
||||
if (adc_cali_handle)
|
||||
{
|
||||
adc_cali_raw_to_voltage(adc_cali_handle, raw, &voltage);
|
||||
}
|
||||
else
|
||||
{
|
||||
voltage = raw * 2500 / 4095;
|
||||
}
|
||||
|
||||
float divider_factor = atof(CONFIG_BATTERY_VOLTAGE_DIVIDER_FACTOR);
|
||||
int battery_voltage = voltage * divider_factor;
|
||||
|
||||
int max_v = CONFIG_BATTERY_MAX_VOLTAGE;
|
||||
int min_v = CONFIG_BATTERY_MIN_VOLTAGE;
|
||||
|
||||
level = (battery_voltage - min_v) * 100 / (max_v - min_v);
|
||||
if (level > 100)
|
||||
level = 100;
|
||||
if (level < 0)
|
||||
level = 0;
|
||||
|
||||
charging = false;
|
||||
discharging = true;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -15,11 +15,13 @@
|
||||
|
||||
#define TAG "Display"
|
||||
|
||||
LvglDisplay::LvglDisplay() {
|
||||
LvglDisplay::LvglDisplay()
|
||||
{
|
||||
// Notification timer
|
||||
esp_timer_create_args_t notification_timer_args = {
|
||||
.callback = [](void *arg) {
|
||||
LvglDisplay *display = static_cast<LvglDisplay*>(arg);
|
||||
.callback = [](void *arg)
|
||||
{
|
||||
LvglDisplay *display = static_cast<LvglDisplay *>(arg);
|
||||
DisplayLockGuard lock(display);
|
||||
lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
@ -33,45 +35,59 @@ LvglDisplay::LvglDisplay() {
|
||||
|
||||
// Create a power management lock
|
||||
auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_);
|
||||
if (ret == ESP_ERR_NOT_SUPPORTED) {
|
||||
if (ret == ESP_ERR_NOT_SUPPORTED)
|
||||
{
|
||||
ESP_LOGI(TAG, "Power management not supported");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_ERROR_CHECK(ret);
|
||||
}
|
||||
}
|
||||
|
||||
LvglDisplay::~LvglDisplay() {
|
||||
if (notification_timer_ != nullptr) {
|
||||
LvglDisplay::~LvglDisplay()
|
||||
{
|
||||
if (notification_timer_ != nullptr)
|
||||
{
|
||||
esp_timer_stop(notification_timer_);
|
||||
esp_timer_delete(notification_timer_);
|
||||
}
|
||||
|
||||
if (network_label_ != nullptr) {
|
||||
if (network_label_ != nullptr)
|
||||
{
|
||||
lv_obj_del(network_label_);
|
||||
}
|
||||
if (notification_label_ != nullptr) {
|
||||
if (notification_label_ != nullptr)
|
||||
{
|
||||
lv_obj_del(notification_label_);
|
||||
}
|
||||
if (status_label_ != nullptr) {
|
||||
if (status_label_ != nullptr)
|
||||
{
|
||||
lv_obj_del(status_label_);
|
||||
}
|
||||
if (mute_label_ != nullptr) {
|
||||
if (mute_label_ != nullptr)
|
||||
{
|
||||
lv_obj_del(mute_label_);
|
||||
}
|
||||
if (battery_label_ != nullptr) {
|
||||
if (battery_label_ != nullptr)
|
||||
{
|
||||
lv_obj_del(battery_label_);
|
||||
}
|
||||
if( low_battery_popup_ != nullptr ) {
|
||||
if (low_battery_popup_ != nullptr)
|
||||
{
|
||||
lv_obj_del(low_battery_popup_);
|
||||
}
|
||||
if (pm_lock_ != nullptr) {
|
||||
if (pm_lock_ != nullptr)
|
||||
{
|
||||
esp_pm_lock_delete(pm_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
void LvglDisplay::SetStatus(const char* status) {
|
||||
void LvglDisplay::SetStatus(const char *status)
|
||||
{
|
||||
DisplayLockGuard lock(this);
|
||||
if (status_label_ == nullptr) {
|
||||
if (status_label_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lv_label_set_text(status_label_, status);
|
||||
@ -81,13 +97,16 @@ void LvglDisplay::SetStatus(const char* status) {
|
||||
last_status_update_time_ = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
void LvglDisplay::ShowNotification(const std::string ¬ification, int duration_ms) {
|
||||
void LvglDisplay::ShowNotification(const std::string ¬ification, int duration_ms)
|
||||
{
|
||||
ShowNotification(notification.c_str(), duration_ms);
|
||||
}
|
||||
|
||||
void LvglDisplay::ShowNotification(const char* notification, int duration_ms) {
|
||||
void LvglDisplay::ShowNotification(const char *notification, int duration_ms)
|
||||
{
|
||||
DisplayLockGuard lock(this);
|
||||
if (notification_label_ == nullptr) {
|
||||
if (notification_label_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lv_label_set_text(notification_label_, notification);
|
||||
@ -98,40 +117,50 @@ void LvglDisplay::ShowNotification(const char* notification, int duration_ms) {
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, duration_ms * 1000));
|
||||
}
|
||||
|
||||
void LvglDisplay::UpdateStatusBar(bool update_all) {
|
||||
auto& app = Application::GetInstance();
|
||||
auto& board = Board::GetInstance();
|
||||
void LvglDisplay::UpdateStatusBar(bool update_all)
|
||||
{
|
||||
auto &app = Application::GetInstance();
|
||||
auto &board = Board::GetInstance();
|
||||
auto codec = board.GetAudioCodec();
|
||||
|
||||
// Update mute icon
|
||||
{
|
||||
DisplayLockGuard lock(this);
|
||||
if (mute_label_ == nullptr) {
|
||||
if (mute_label_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果静音状态改变,则更新图标
|
||||
if (codec->output_volume() == 0 && !muted_) {
|
||||
if (codec->output_volume() == 0 && !muted_)
|
||||
{
|
||||
muted_ = true;
|
||||
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK);
|
||||
} else if (codec->output_volume() > 0 && muted_) {
|
||||
}
|
||||
else if (codec->output_volume() > 0 && muted_)
|
||||
{
|
||||
muted_ = false;
|
||||
lv_label_set_text(mute_label_, "");
|
||||
}
|
||||
}
|
||||
|
||||
// Update time
|
||||
if (app.GetDeviceState() == kDeviceStateIdle) {
|
||||
if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now()) {
|
||||
if (app.GetDeviceState() == kDeviceStateIdle)
|
||||
{
|
||||
if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now())
|
||||
{
|
||||
// Set status to clock "HH:MM"
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm = localtime(&now);
|
||||
struct tm *tm = localtime(&now);
|
||||
// Check if the we have already set the time
|
||||
if (tm->tm_year >= 2025 - 1900) {
|
||||
if (tm->tm_year >= 2025 - 1900)
|
||||
{
|
||||
char time_str[16];
|
||||
strftime(time_str, sizeof(time_str), "%H:%M ", tm);
|
||||
SetStatus(time_str);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "System time is not set, tm_year: %d", tm->tm_year);
|
||||
}
|
||||
}
|
||||
@ -141,36 +170,60 @@ void LvglDisplay::UpdateStatusBar(bool update_all) {
|
||||
// 更新电池图标
|
||||
int battery_level;
|
||||
bool charging, discharging;
|
||||
const char* icon = nullptr;
|
||||
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
|
||||
if (charging) {
|
||||
const char *icon = nullptr;
|
||||
if (board.GetBatteryLevel(battery_level, charging, discharging))
|
||||
{
|
||||
if (charging)
|
||||
{
|
||||
icon = FONT_AWESOME_BATTERY_BOLT;
|
||||
} else {
|
||||
const char* levels[] = {
|
||||
FONT_AWESOME_BATTERY_EMPTY, // 0-19%
|
||||
FONT_AWESOME_BATTERY_QUARTER, // 20-39%
|
||||
FONT_AWESOME_BATTERY_HALF, // 40-59%
|
||||
FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79%
|
||||
FONT_AWESOME_BATTERY_FULL, // 80-99%
|
||||
FONT_AWESOME_BATTERY_FULL, // 100%
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *levels[] = {
|
||||
FONT_AWESOME_BATTERY_EMPTY, // 0-19%
|
||||
FONT_AWESOME_BATTERY_QUARTER, // 20-39%
|
||||
FONT_AWESOME_BATTERY_HALF, // 40-59%
|
||||
FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79%
|
||||
FONT_AWESOME_BATTERY_FULL, // 80-99%
|
||||
FONT_AWESOME_BATTERY_FULL, // 100%
|
||||
};
|
||||
icon = levels[battery_level / 20];
|
||||
int index = battery_level / 20;
|
||||
if (index > 5)
|
||||
index = 5;
|
||||
icon = levels[index];
|
||||
}
|
||||
DisplayLockGuard lock(this);
|
||||
if (battery_label_ != nullptr && battery_icon_ != icon) {
|
||||
battery_icon_ = icon;
|
||||
lv_label_set_text(battery_label_, battery_icon_);
|
||||
if (battery_label_ != nullptr)
|
||||
{
|
||||
char battery_text[32];
|
||||
snprintf(battery_text, sizeof(battery_text), "%s %d%%", icon, battery_level);
|
||||
lv_label_set_text(battery_label_, battery_text);
|
||||
|
||||
if (battery_level < 20 && discharging)
|
||||
{
|
||||
lv_obj_set_style_text_color(battery_label_, lv_palette_main(LV_PALETTE_RED), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
lv_obj_set_style_text_color(battery_label_, lv_color_white(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (low_battery_popup_ != nullptr) {
|
||||
if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) {
|
||||
if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示
|
||||
if (low_battery_popup_ != nullptr)
|
||||
{
|
||||
if (battery_level < 20 && discharging)
|
||||
{
|
||||
if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN))
|
||||
{ // 如果低电量提示框隐藏,则显示
|
||||
lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide the low battery popup when the battery is not empty
|
||||
if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框显示,则隐藏
|
||||
if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN))
|
||||
{ // 如果低电量提示框显示,则隐藏
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
@ -179,7 +232,8 @@ void LvglDisplay::UpdateStatusBar(bool update_all) {
|
||||
|
||||
// 每 10 秒更新一次网络图标
|
||||
static int seconds_counter = 0;
|
||||
if (update_all || seconds_counter++ % 10 == 0) {
|
||||
if (update_all || seconds_counter++ % 10 == 0)
|
||||
{
|
||||
// 升级固件时,不读取 4G 网络状态,避免占用 UART 资源
|
||||
auto device_state = Application::GetInstance().GetDeviceState();
|
||||
static const std::vector<DeviceState> allowed_states = {
|
||||
@ -189,9 +243,11 @@ void LvglDisplay::UpdateStatusBar(bool update_all) {
|
||||
kDeviceStateListening,
|
||||
kDeviceStateActivating,
|
||||
};
|
||||
if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) {
|
||||
if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end())
|
||||
{
|
||||
icon = board.GetNetworkStateIcon();
|
||||
if (network_label_ != nullptr && icon != nullptr && network_icon_ != icon) {
|
||||
if (network_label_ != nullptr && icon != nullptr && network_icon_ != icon)
|
||||
{
|
||||
DisplayLockGuard lock(this);
|
||||
network_icon_ = icon;
|
||||
lv_label_set_text(network_label_, network_icon_);
|
||||
@ -202,34 +258,42 @@ void LvglDisplay::UpdateStatusBar(bool update_all) {
|
||||
esp_pm_lock_release(pm_lock_);
|
||||
}
|
||||
|
||||
void LvglDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
|
||||
void LvglDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image)
|
||||
{
|
||||
}
|
||||
|
||||
void LvglDisplay::SetPowerSaveMode(bool on) {
|
||||
if (on) {
|
||||
void LvglDisplay::SetPowerSaveMode(bool on)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
SetChatMessage("system", "");
|
||||
SetEmotion("sleepy");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
SetChatMessage("system", "");
|
||||
SetEmotion("neutral");
|
||||
}
|
||||
}
|
||||
|
||||
bool LvglDisplay::SnapshotToJpeg(std::string& jpeg_data, int quality) {
|
||||
bool LvglDisplay::SnapshotToJpeg(std::string &jpeg_data, int quality)
|
||||
{
|
||||
#if CONFIG_LV_USE_SNAPSHOT
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
lv_obj_t* screen = lv_screen_active();
|
||||
lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565);
|
||||
if (draw_buffer == nullptr) {
|
||||
lv_obj_t *screen = lv_screen_active();
|
||||
lv_draw_buf_t *draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565);
|
||||
if (draw_buffer == nullptr)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to take snapshot, draw_buffer is nullptr");
|
||||
return false;
|
||||
}
|
||||
|
||||
// swap bytes
|
||||
uint16_t* data = (uint16_t*)draw_buffer->data;
|
||||
uint16_t *data = (uint16_t *)draw_buffer->data;
|
||||
size_t pixel_count = draw_buffer->data_size / 2;
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
for (size_t i = 0; i < pixel_count; i++)
|
||||
{
|
||||
data[i] = __builtin_bswap16(data[i]);
|
||||
}
|
||||
|
||||
@ -237,15 +301,19 @@ bool LvglDisplay::SnapshotToJpeg(std::string& jpeg_data, int quality) {
|
||||
jpeg_data.clear();
|
||||
|
||||
// 🚀 使用回调版本的JPEG编码器,进一步节省内存
|
||||
bool ret = image_to_jpeg_cb((uint8_t*)draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h, V4L2_PIX_FMT_RGB565, quality,
|
||||
[](void *arg, size_t index, const void *data, size_t len) -> size_t {
|
||||
std::string* output = static_cast<std::string*>(arg);
|
||||
if (data && len > 0) {
|
||||
output->append(static_cast<const char*>(data), len);
|
||||
}
|
||||
return len;
|
||||
}, &jpeg_data);
|
||||
if (!ret) {
|
||||
bool ret = image_to_jpeg_cb((uint8_t *)draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h, V4L2_PIX_FMT_RGB565, quality,
|
||||
[](void *arg, size_t index, const void *data, size_t len) -> size_t
|
||||
{
|
||||
std::string *output = static_cast<std::string *>(arg);
|
||||
if (data && len > 0)
|
||||
{
|
||||
output->append(static_cast<const char *>(data), len);
|
||||
}
|
||||
return len;
|
||||
},
|
||||
&jpeg_data);
|
||||
if (!ret)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to convert image to JPEG");
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user