forked from xiaozhi/xiaozhi-esp32
sensecap watcher manufacture (#469)
* feat: add shutdown and battery cmd. * fix: fixed the issue that the LCD does not light up when some devices are turned on. * fix: fix task sys_evt stack overflow. * feat: Optimize UI display for circles; add Added factory reset function. * feat: "low_battery_label_" obj configurable * feat: add read_mac cmd * fix: fix "low_battery_label_" obj redefine * style: modify Google C++ Style. * Update sensecap_watcher.cc Remove extra spaces --------- Co-authored-by: Xiaoxia <terrence@tenclass.com>
This commit is contained in:
@@ -98,4 +98,9 @@
|
||||
|
||||
#define CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV 16
|
||||
|
||||
/* ADC */
|
||||
#define BSP_BAT_ADC_CHAN (ADC_CHANNEL_2) // GPIO3
|
||||
#define BSP_BAT_ADC_ATTEN (ADC_ATTEN_DB_2_5) // 0 ~ 1100 mV
|
||||
#define BSP_BAT_VOL_RATIO ((62 + 20) / 20)
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
@@ -25,12 +25,64 @@
|
||||
#include <iot_knob.h>
|
||||
#include <esp_io_expander_tca95xx_16bit.h>
|
||||
#include <esp_sleep.h>
|
||||
#include "esp_console.h"
|
||||
#include "esp_mac.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#define TAG "sensecap_watcher"
|
||||
|
||||
|
||||
LV_FONT_DECLARE(font_puhui_30_4);
|
||||
LV_FONT_DECLARE(font_awesome_30_4);
|
||||
LV_FONT_DECLARE(font_awesome_20_4);
|
||||
|
||||
class CustomLcdDisplay : public SpiLcdDisplay {
|
||||
public:
|
||||
CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
|
||||
esp_lcd_panel_handle_t panel_handle,
|
||||
int width,
|
||||
int height,
|
||||
int offset_x,
|
||||
int offset_y,
|
||||
bool mirror_x,
|
||||
bool mirror_y,
|
||||
bool swap_xy)
|
||||
: SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy,
|
||||
{
|
||||
.text_font = &font_puhui_30_4,
|
||||
.icon_font = &font_awesome_20_4,
|
||||
.emoji_font = font_emoji_64_init(),
|
||||
}) {
|
||||
|
||||
DisplayLockGuard lock(this);
|
||||
lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height * 2 + 10);
|
||||
lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0);
|
||||
lv_obj_set_style_pad_top(status_bar_, 10, 0);
|
||||
lv_obj_set_style_pad_bottom(status_bar_, 1, 0);
|
||||
|
||||
// 针对圆形屏幕调整位置
|
||||
// network battery mute //
|
||||
// status //
|
||||
lv_obj_align(battery_label_, LV_ALIGN_TOP_MID, -2.5*fonts_.icon_font->line_height, 0);
|
||||
lv_obj_align(network_label_, LV_ALIGN_TOP_MID, -0.5*fonts_.icon_font->line_height, 0);
|
||||
lv_obj_align(mute_label_, LV_ALIGN_TOP_MID, 1.5*fonts_.icon_font->line_height, 0);
|
||||
|
||||
lv_obj_align(status_label_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_flex_grow(status_label_, 0);
|
||||
lv_obj_set_width(status_label_, LV_HOR_RES * 0.75);
|
||||
lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
|
||||
lv_obj_align(notification_label_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_width(notification_label_, LV_HOR_RES * 0.75);
|
||||
lv_label_set_long_mode(notification_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
|
||||
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -20);
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, lv_color_hex(0xFF0000), 0);
|
||||
lv_obj_set_width(low_battery_label_, LV_HOR_RES * 0.75);
|
||||
lv_label_set_long_mode(low_battery_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
}
|
||||
};
|
||||
|
||||
class SensecapWatcher : public WifiBoard {
|
||||
private:
|
||||
@@ -42,7 +94,7 @@ private:
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
|
||||
uint32_t long_press_cnt_;
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
@@ -144,7 +196,7 @@ private:
|
||||
void OnKnobRotate(bool clockwise) {
|
||||
auto codec = GetAudioCodec();
|
||||
int current_volume = codec->output_volume();
|
||||
int new_volume = current_volume + (clockwise ? 5 : -5);
|
||||
int new_volume = current_volume + (clockwise ? -5 : 5);
|
||||
|
||||
// 确保音量在有效范围内
|
||||
if (new_volume > 100) {
|
||||
@@ -163,7 +215,7 @@ private:
|
||||
ESP_LOGE(TAG, "Failed to set volume! Expected:%d Actual:%d",
|
||||
new_volume, codec->output_volume());
|
||||
}
|
||||
GetDisplay()->ShowNotification("音量: " + std::to_string(codec->output_volume()));
|
||||
GetDisplay()->ShowNotification(std::string(Lang::Strings::VOLUME) + ": "+std::to_string(codec->output_volume()));
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
|
||||
@@ -193,7 +245,7 @@ private:
|
||||
},
|
||||
};
|
||||
|
||||
//watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击
|
||||
// watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击
|
||||
ESP_LOGI(TAG, "waiting for knob button release");
|
||||
while(IoExpanderGetLevel(BSP_KNOB_BTN) == 0) {
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
@@ -212,6 +264,7 @@ private:
|
||||
iot_button_register_cb(btns, BUTTON_LONG_PRESS_START, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<SensecapWatcher*>(usr_data);
|
||||
bool is_charging = (self->IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
|
||||
self->long_press_cnt_ = 0;
|
||||
if (is_charging) {
|
||||
ESP_LOGI(TAG, "charging");
|
||||
} else {
|
||||
@@ -219,6 +272,18 @@ private:
|
||||
self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
|
||||
}
|
||||
}, this);
|
||||
|
||||
iot_button_register_cb(btns, BUTTON_LONG_PRESS_HOLD, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<SensecapWatcher*>(usr_data);
|
||||
self->long_press_cnt_++; // 每隔20ms加一
|
||||
// 长按10s 恢复出厂设置: 2+0.02*400 = 10
|
||||
if (self->long_press_cnt_ > 400) {
|
||||
ESP_LOGI(TAG, "Factory reset");
|
||||
nvs_flash_erase();
|
||||
esp_restart();
|
||||
}
|
||||
}, this);
|
||||
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
@@ -270,23 +335,18 @@ private:
|
||||
esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
esp_lcd_panel_disp_on_off(panel_, true);
|
||||
|
||||
display_ = new SpiLcdDisplay(panel_io_, panel_,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
|
||||
{
|
||||
.text_font = &font_puhui_30_4,
|
||||
.icon_font = &font_awesome_30_4,
|
||||
.emoji_font = font_emoji_64_init(),
|
||||
});
|
||||
display_ = new CustomLcdDisplay(panel_io_, panel_,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
|
||||
// 使每次刷新的起始列数索引是4的倍数且列数总数是4的倍数,以满足SPD2010的要求
|
||||
lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t *e) {
|
||||
lv_area_t *area = (lv_area_t *)lv_event_get_param(e);
|
||||
uint16_t x1 = area->x1;
|
||||
uint16_t x2 = area->x2;
|
||||
// round the start of area down to the nearest 4N number
|
||||
area->x1 = (x1 >> 2) << 2;
|
||||
// round the end of area up to the nearest 4M+3 number
|
||||
area->x2 = ((x2 >> 2) << 2) + 3;
|
||||
lv_area_t *area = (lv_area_t *)lv_event_get_param(e);
|
||||
uint16_t x1 = area->x1;
|
||||
uint16_t x2 = area->x2;
|
||||
// round the start of area down to the nearest 4N number
|
||||
area->x1 = (x1 >> 2) << 2;
|
||||
// round the end of area up to the nearest 4M+3 number
|
||||
area->x2 = ((x2 >> 2) << 2) + 3;
|
||||
}, LV_EVENT_INVALIDATE_AREA, NULL);
|
||||
|
||||
}
|
||||
@@ -296,15 +356,159 @@ private:
|
||||
auto& thing_manager = iot::ThingManager::GetInstance();
|
||||
thing_manager.AddThing(iot::CreateThing("Speaker"));
|
||||
thing_manager.AddThing(iot::CreateThing("Screen"));
|
||||
thing_manager.AddThing(iot::CreateThing("Battery"));
|
||||
}
|
||||
|
||||
uint16_t BatterygetVoltage(void) {
|
||||
static bool initialized = false;
|
||||
static adc_oneshot_unit_handle_t adc_handle;
|
||||
static adc_cali_handle_t cali_handle = NULL;
|
||||
if (!initialized) {
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
adc_oneshot_new_unit(&init_config, &adc_handle);
|
||||
|
||||
adc_oneshot_chan_cfg_t ch_config = {
|
||||
.atten = BSP_BAT_ADC_ATTEN,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
adc_oneshot_config_channel(adc_handle, BSP_BAT_ADC_CHAN, &ch_config);
|
||||
|
||||
adc_cali_curve_fitting_config_t cali_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.chan = BSP_BAT_ADC_CHAN,
|
||||
.atten = BSP_BAT_ADC_ATTEN,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK) {
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
if (initialized) {
|
||||
int raw_value = 0;
|
||||
int voltage = 0; // mV
|
||||
adc_oneshot_read(adc_handle, BSP_BAT_ADC_CHAN, &raw_value);
|
||||
adc_cali_raw_to_voltage(cali_handle, raw_value, &voltage);
|
||||
voltage = voltage * 82 / 20;
|
||||
// ESP_LOGI(TAG, "voltage: %dmV", voltage);
|
||||
return (uint16_t)voltage;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t BatterygetPercent(bool print = false) {
|
||||
int voltage = 0;
|
||||
for (uint8_t i = 0; i < 10; i++) {
|
||||
voltage += BatterygetVoltage();
|
||||
}
|
||||
voltage /= 10;
|
||||
int percent = (-1 * voltage * voltage + 9016 * voltage - 19189000) / 10000;
|
||||
percent = (percent > 100) ? 100 : (percent < 0) ? 0 : percent;
|
||||
if (print) {
|
||||
printf("voltage: %dmV, percentage: %d%%\r\n", voltage, percent);
|
||||
}
|
||||
return (uint8_t)percent;
|
||||
}
|
||||
|
||||
void InitializeCmd() {
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
repl_config.max_cmdline_length = 1024;
|
||||
repl_config.prompt = "SenseCAP>";
|
||||
|
||||
const esp_console_cmd_t cmd1 = {
|
||||
.command = "reboot",
|
||||
.help = "reboot the device",
|
||||
.hint = nullptr,
|
||||
.func = [](int argc, char** argv) -> int {
|
||||
esp_restart();
|
||||
return 0;
|
||||
},
|
||||
.argtable = nullptr
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd1));
|
||||
|
||||
const esp_console_cmd_t cmd2 = {
|
||||
.command = "shutdown",
|
||||
.help = "shutdown the device",
|
||||
.hint = nullptr,
|
||||
.func = NULL,
|
||||
.argtable = NULL,
|
||||
.func_w_context = [](void *context,int argc, char** argv) -> int {
|
||||
auto self = static_cast<SensecapWatcher*>(context);
|
||||
self->GetBacklight()->SetBrightness(0);
|
||||
self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
|
||||
return 0;
|
||||
},
|
||||
.context =this
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd2));
|
||||
|
||||
const esp_console_cmd_t cmd3 = {
|
||||
.command = "battery",
|
||||
.help = "get battery percent",
|
||||
.hint = NULL,
|
||||
.func = NULL,
|
||||
.argtable = NULL,
|
||||
.func_w_context = [](void *context,int argc, char** argv) -> int {
|
||||
auto self = static_cast<SensecapWatcher*>(context);
|
||||
self->BatterygetPercent(true);
|
||||
return 0;
|
||||
},
|
||||
.context =this
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd3));
|
||||
|
||||
const esp_console_cmd_t cmd4 = {
|
||||
.command = "factory_reset",
|
||||
.help = "factory reset and reboot the device",
|
||||
.hint = NULL,
|
||||
.func = NULL,
|
||||
.argtable = NULL,
|
||||
.func_w_context = [](void *context,int argc, char** argv) -> int {
|
||||
auto self = static_cast<SensecapWatcher*>(context);
|
||||
nvs_flash_erase();
|
||||
esp_restart();
|
||||
return 0;
|
||||
},
|
||||
.context =this
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd4));
|
||||
|
||||
const esp_console_cmd_t cmd5 = {
|
||||
.command = "read_mac",
|
||||
.help = "Read mac address",
|
||||
.hint = NULL,
|
||||
.func = NULL,
|
||||
.argtable = NULL,
|
||||
.func_w_context = [](void *context,int argc, char** argv) -> int {
|
||||
uint8_t mac[6];
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
printf("wifi_sta_mac: " MACSTR "\n", MAC2STR(mac));
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP);
|
||||
printf("wifi_softap_mac: " MACSTR "\n", MAC2STR(mac));
|
||||
esp_read_mac(mac, ESP_MAC_BT);
|
||||
printf("bt_mac: " MACSTR "\n", MAC2STR(mac));
|
||||
return 0;
|
||||
},
|
||||
.context =this
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd5));
|
||||
|
||||
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
}
|
||||
|
||||
public:
|
||||
SensecapWatcher(){
|
||||
SensecapWatcher() {
|
||||
ESP_LOGI(TAG, "Initialize Sensecap Watcher");
|
||||
InitializePowerSaveTimer();
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeExpander();
|
||||
InitializeCmd(); //工厂生产测试使用
|
||||
InitializeButton();
|
||||
InitializeKnob();
|
||||
Initializespd2010Display();
|
||||
@@ -352,6 +556,23 @@ public:
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
|
||||
discharging = !charging;
|
||||
level = (int)BatterygetPercent(false);
|
||||
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
if (level <= 1 && discharging) {
|
||||
ESP_LOGI(TAG, "Battery level is low, shutting down");
|
||||
IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(SensecapWatcher);
|
||||
|
||||
@@ -75,7 +75,9 @@ Display::~Display() {
|
||||
lv_obj_del(battery_label_);
|
||||
lv_obj_del(emotion_label_);
|
||||
}
|
||||
|
||||
if( low_battery_popup_ != nullptr ) {
|
||||
lv_obj_del(low_battery_popup_);
|
||||
}
|
||||
if (pm_lock_ != nullptr) {
|
||||
esp_pm_lock_delete(pm_lock_);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ protected:
|
||||
lv_obj_t *battery_label_ = nullptr;
|
||||
lv_obj_t* chat_message_label_ = nullptr;
|
||||
lv_obj_t* low_battery_popup_ = nullptr;
|
||||
|
||||
lv_obj_t* low_battery_label_ = nullptr;
|
||||
|
||||
const char* battery_icon_ = nullptr;
|
||||
const char* network_icon_ = nullptr;
|
||||
bool muted_ = false;
|
||||
|
||||
@@ -363,10 +363,10 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, current_theme.low_battery, 0);
|
||||
lv_obj_set_style_radius(low_battery_popup_, 10, 0);
|
||||
lv_obj_t* low_battery_label = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
lv_obj_set_style_text_color(low_battery_label, lv_color_white(), 0);
|
||||
lv_obj_center(low_battery_label);
|
||||
low_battery_label_ = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
|
||||
lv_obj_center(low_battery_label_);
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
@@ -628,10 +628,10 @@ void LcdDisplay::SetupUI() {
|
||||
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, current_theme.low_battery, 0);
|
||||
lv_obj_set_style_radius(low_battery_popup_, 10, 0);
|
||||
lv_obj_t* low_battery_label = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
lv_obj_set_style_text_color(low_battery_label, lv_color_white(), 0);
|
||||
lv_obj_center(low_battery_label);
|
||||
low_battery_label_ = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
|
||||
lv_obj_center(low_battery_label_);
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -218,10 +218,10 @@ void OledDisplay::SetupUI_128x64() {
|
||||
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0);
|
||||
lv_obj_set_style_radius(low_battery_popup_, 10, 0);
|
||||
lv_obj_t* low_battery_label = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
lv_obj_set_style_text_color(low_battery_label, lv_color_white(), 0);
|
||||
lv_obj_center(low_battery_label);
|
||||
low_battery_label_ = lv_label_create(low_battery_popup_);
|
||||
lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
|
||||
lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
|
||||
lv_obj_center(low_battery_label_);
|
||||
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user