Skip to content

Commit 076d907

Browse files
Wvirgil12378
andauthored
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 <[email protected]>
1 parent 04c0da0 commit 076d907

File tree

6 files changed

+263
-34
lines changed

6 files changed

+263
-34
lines changed

main/boards/sensecap-watcher/config.h

+5
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,9 @@
9898

9999
#define CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV 16
100100

101+
/* ADC */
102+
#define BSP_BAT_ADC_CHAN (ADC_CHANNEL_2) // GPIO3
103+
#define BSP_BAT_ADC_ATTEN (ADC_ATTEN_DB_2_5) // 0 ~ 1100 mV
104+
#define BSP_BAT_VOL_RATIO ((62 + 20) / 20)
105+
101106
#endif // _BOARD_CONFIG_H_

main/boards/sensecap-watcher/sensecap_watcher.cc

+241-20
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,64 @@
2525
#include <iot_knob.h>
2626
#include <esp_io_expander_tca95xx_16bit.h>
2727
#include <esp_sleep.h>
28+
#include "esp_console.h"
29+
#include "esp_mac.h"
30+
#include "nvs_flash.h"
31+
32+
#include "assets/lang_config.h"
2833

2934
#define TAG "sensecap_watcher"
3035

3136

3237
LV_FONT_DECLARE(font_puhui_30_4);
33-
LV_FONT_DECLARE(font_awesome_30_4);
38+
LV_FONT_DECLARE(font_awesome_20_4);
39+
40+
class CustomLcdDisplay : public SpiLcdDisplay {
41+
public:
42+
CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
43+
esp_lcd_panel_handle_t panel_handle,
44+
int width,
45+
int height,
46+
int offset_x,
47+
int offset_y,
48+
bool mirror_x,
49+
bool mirror_y,
50+
bool swap_xy)
51+
: SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy,
52+
{
53+
.text_font = &font_puhui_30_4,
54+
.icon_font = &font_awesome_20_4,
55+
.emoji_font = font_emoji_64_init(),
56+
}) {
57+
58+
DisplayLockGuard lock(this);
59+
lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height * 2 + 10);
60+
lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0);
61+
lv_obj_set_style_pad_top(status_bar_, 10, 0);
62+
lv_obj_set_style_pad_bottom(status_bar_, 1, 0);
63+
64+
// 针对圆形屏幕调整位置
65+
// network battery mute //
66+
// status //
67+
lv_obj_align(battery_label_, LV_ALIGN_TOP_MID, -2.5*fonts_.icon_font->line_height, 0);
68+
lv_obj_align(network_label_, LV_ALIGN_TOP_MID, -0.5*fonts_.icon_font->line_height, 0);
69+
lv_obj_align(mute_label_, LV_ALIGN_TOP_MID, 1.5*fonts_.icon_font->line_height, 0);
70+
71+
lv_obj_align(status_label_, LV_ALIGN_BOTTOM_MID, 0, 0);
72+
lv_obj_set_flex_grow(status_label_, 0);
73+
lv_obj_set_width(status_label_, LV_HOR_RES * 0.75);
74+
lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
75+
76+
lv_obj_align(notification_label_, LV_ALIGN_BOTTOM_MID, 0, 0);
77+
lv_obj_set_width(notification_label_, LV_HOR_RES * 0.75);
78+
lv_label_set_long_mode(notification_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
79+
80+
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -20);
81+
lv_obj_set_style_bg_color(low_battery_popup_, lv_color_hex(0xFF0000), 0);
82+
lv_obj_set_width(low_battery_label_, LV_HOR_RES * 0.75);
83+
lv_label_set_long_mode(low_battery_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
84+
}
85+
};
3486

3587
class SensecapWatcher : public WifiBoard {
3688
private:
@@ -42,7 +94,7 @@ class SensecapWatcher : public WifiBoard {
4294
PowerSaveTimer* power_save_timer_;
4395
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
4496
esp_lcd_panel_handle_t panel_ = nullptr;
45-
97+
uint32_t long_press_cnt_;
4698
void InitializePowerSaveTimer() {
4799
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
48100
power_save_timer_->OnEnterSleepMode([this]() {
@@ -144,7 +196,7 @@ class SensecapWatcher : public WifiBoard {
144196
void OnKnobRotate(bool clockwise) {
145197
auto codec = GetAudioCodec();
146198
int current_volume = codec->output_volume();
147-
int new_volume = current_volume + (clockwise ? 5 : -5);
199+
int new_volume = current_volume + (clockwise ? -5 : 5);
148200

149201
// 确保音量在有效范围内
150202
if (new_volume > 100) {
@@ -163,7 +215,7 @@ class SensecapWatcher : public WifiBoard {
163215
ESP_LOGE(TAG, "Failed to set volume! Expected:%d Actual:%d",
164216
new_volume, codec->output_volume());
165217
}
166-
GetDisplay()->ShowNotification("音量: " + std::to_string(codec->output_volume()));
218+
GetDisplay()->ShowNotification(std::string(Lang::Strings::VOLUME) + ": "+std::to_string(codec->output_volume()));
167219
power_save_timer_->WakeUp();
168220
}
169221

@@ -193,7 +245,7 @@ class SensecapWatcher : public WifiBoard {
193245
},
194246
};
195247

196-
//watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击
248+
// watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击
197249
ESP_LOGI(TAG, "waiting for knob button release");
198250
while(IoExpanderGetLevel(BSP_KNOB_BTN) == 0) {
199251
vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -212,13 +264,26 @@ class SensecapWatcher : public WifiBoard {
212264
iot_button_register_cb(btns, BUTTON_LONG_PRESS_START, [](void* button_handle, void* usr_data) {
213265
auto self = static_cast<SensecapWatcher*>(usr_data);
214266
bool is_charging = (self->IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
267+
self->long_press_cnt_ = 0;
215268
if (is_charging) {
216269
ESP_LOGI(TAG, "charging");
217270
} else {
218271
self->IoExpanderSetLevel(BSP_PWR_LCD, 0);
219272
self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
220273
}
221274
}, this);
275+
276+
iot_button_register_cb(btns, BUTTON_LONG_PRESS_HOLD, [](void* button_handle, void* usr_data) {
277+
auto self = static_cast<SensecapWatcher*>(usr_data);
278+
self->long_press_cnt_++; // 每隔20ms加一
279+
// 长按10s 恢复出厂设置: 2+0.02*400 = 10
280+
if (self->long_press_cnt_ > 400) {
281+
ESP_LOGI(TAG, "Factory reset");
282+
nvs_flash_erase();
283+
esp_restart();
284+
}
285+
}, this);
286+
222287
}
223288

224289
void InitializeSpi() {
@@ -270,23 +335,18 @@ class SensecapWatcher : public WifiBoard {
270335
esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
271336
esp_lcd_panel_disp_on_off(panel_, true);
272337

273-
display_ = new SpiLcdDisplay(panel_io_, panel_,
274-
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
275-
{
276-
.text_font = &font_puhui_30_4,
277-
.icon_font = &font_awesome_30_4,
278-
.emoji_font = font_emoji_64_init(),
279-
});
338+
display_ = new CustomLcdDisplay(panel_io_, panel_,
339+
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
280340

281341
// 使每次刷新的起始列数索引是4的倍数且列数总数是4的倍数,以满足SPD2010的要求
282342
lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t *e) {
283-
lv_area_t *area = (lv_area_t *)lv_event_get_param(e);
284-
uint16_t x1 = area->x1;
285-
uint16_t x2 = area->x2;
286-
// round the start of area down to the nearest 4N number
287-
area->x1 = (x1 >> 2) << 2;
288-
// round the end of area up to the nearest 4M+3 number
289-
area->x2 = ((x2 >> 2) << 2) + 3;
343+
lv_area_t *area = (lv_area_t *)lv_event_get_param(e);
344+
uint16_t x1 = area->x1;
345+
uint16_t x2 = area->x2;
346+
// round the start of area down to the nearest 4N number
347+
area->x1 = (x1 >> 2) << 2;
348+
// round the end of area up to the nearest 4M+3 number
349+
area->x2 = ((x2 >> 2) << 2) + 3;
290350
}, LV_EVENT_INVALIDATE_AREA, NULL);
291351

292352
}
@@ -296,15 +356,159 @@ class SensecapWatcher : public WifiBoard {
296356
auto& thing_manager = iot::ThingManager::GetInstance();
297357
thing_manager.AddThing(iot::CreateThing("Speaker"));
298358
thing_manager.AddThing(iot::CreateThing("Screen"));
359+
thing_manager.AddThing(iot::CreateThing("Battery"));
360+
}
361+
362+
uint16_t BatterygetVoltage(void) {
363+
static bool initialized = false;
364+
static adc_oneshot_unit_handle_t adc_handle;
365+
static adc_cali_handle_t cali_handle = NULL;
366+
if (!initialized) {
367+
adc_oneshot_unit_init_cfg_t init_config = {
368+
.unit_id = ADC_UNIT_1,
369+
};
370+
adc_oneshot_new_unit(&init_config, &adc_handle);
371+
372+
adc_oneshot_chan_cfg_t ch_config = {
373+
.atten = BSP_BAT_ADC_ATTEN,
374+
.bitwidth = ADC_BITWIDTH_DEFAULT,
375+
};
376+
adc_oneshot_config_channel(adc_handle, BSP_BAT_ADC_CHAN, &ch_config);
377+
378+
adc_cali_curve_fitting_config_t cali_config = {
379+
.unit_id = ADC_UNIT_1,
380+
.chan = BSP_BAT_ADC_CHAN,
381+
.atten = BSP_BAT_ADC_ATTEN,
382+
.bitwidth = ADC_BITWIDTH_DEFAULT,
383+
};
384+
if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK) {
385+
initialized = true;
386+
}
387+
}
388+
if (initialized) {
389+
int raw_value = 0;
390+
int voltage = 0; // mV
391+
adc_oneshot_read(adc_handle, BSP_BAT_ADC_CHAN, &raw_value);
392+
adc_cali_raw_to_voltage(cali_handle, raw_value, &voltage);
393+
voltage = voltage * 82 / 20;
394+
// ESP_LOGI(TAG, "voltage: %dmV", voltage);
395+
return (uint16_t)voltage;
396+
}
397+
return 0;
398+
}
399+
400+
uint8_t BatterygetPercent(bool print = false) {
401+
int voltage = 0;
402+
for (uint8_t i = 0; i < 10; i++) {
403+
voltage += BatterygetVoltage();
404+
}
405+
voltage /= 10;
406+
int percent = (-1 * voltage * voltage + 9016 * voltage - 19189000) / 10000;
407+
percent = (percent > 100) ? 100 : (percent < 0) ? 0 : percent;
408+
if (print) {
409+
printf("voltage: %dmV, percentage: %d%%\r\n", voltage, percent);
410+
}
411+
return (uint8_t)percent;
412+
}
413+
414+
void InitializeCmd() {
415+
esp_console_repl_t *repl = NULL;
416+
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
417+
repl_config.max_cmdline_length = 1024;
418+
repl_config.prompt = "SenseCAP>";
419+
420+
const esp_console_cmd_t cmd1 = {
421+
.command = "reboot",
422+
.help = "reboot the device",
423+
.hint = nullptr,
424+
.func = [](int argc, char** argv) -> int {
425+
esp_restart();
426+
return 0;
427+
},
428+
.argtable = nullptr
429+
};
430+
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd1));
431+
432+
const esp_console_cmd_t cmd2 = {
433+
.command = "shutdown",
434+
.help = "shutdown the device",
435+
.hint = nullptr,
436+
.func = NULL,
437+
.argtable = NULL,
438+
.func_w_context = [](void *context,int argc, char** argv) -> int {
439+
auto self = static_cast<SensecapWatcher*>(context);
440+
self->GetBacklight()->SetBrightness(0);
441+
self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
442+
return 0;
443+
},
444+
.context =this
445+
};
446+
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd2));
447+
448+
const esp_console_cmd_t cmd3 = {
449+
.command = "battery",
450+
.help = "get battery percent",
451+
.hint = NULL,
452+
.func = NULL,
453+
.argtable = NULL,
454+
.func_w_context = [](void *context,int argc, char** argv) -> int {
455+
auto self = static_cast<SensecapWatcher*>(context);
456+
self->BatterygetPercent(true);
457+
return 0;
458+
},
459+
.context =this
460+
};
461+
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd3));
462+
463+
const esp_console_cmd_t cmd4 = {
464+
.command = "factory_reset",
465+
.help = "factory reset and reboot the device",
466+
.hint = NULL,
467+
.func = NULL,
468+
.argtable = NULL,
469+
.func_w_context = [](void *context,int argc, char** argv) -> int {
470+
auto self = static_cast<SensecapWatcher*>(context);
471+
nvs_flash_erase();
472+
esp_restart();
473+
return 0;
474+
},
475+
.context =this
476+
};
477+
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd4));
478+
479+
const esp_console_cmd_t cmd5 = {
480+
.command = "read_mac",
481+
.help = "Read mac address",
482+
.hint = NULL,
483+
.func = NULL,
484+
.argtable = NULL,
485+
.func_w_context = [](void *context,int argc, char** argv) -> int {
486+
uint8_t mac[6];
487+
esp_read_mac(mac, ESP_MAC_WIFI_STA);
488+
printf("wifi_sta_mac: " MACSTR "\n", MAC2STR(mac));
489+
esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP);
490+
printf("wifi_softap_mac: " MACSTR "\n", MAC2STR(mac));
491+
esp_read_mac(mac, ESP_MAC_BT);
492+
printf("bt_mac: " MACSTR "\n", MAC2STR(mac));
493+
return 0;
494+
},
495+
.context =this
496+
};
497+
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd5));
498+
499+
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
500+
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
501+
ESP_ERROR_CHECK(esp_console_start_repl(repl));
299502
}
300503

301504
public:
302-
SensecapWatcher(){
505+
SensecapWatcher() {
303506
ESP_LOGI(TAG, "Initialize Sensecap Watcher");
304507
InitializePowerSaveTimer();
305508
InitializeI2c();
306509
InitializeSpi();
307510
InitializeExpander();
511+
InitializeCmd(); //工厂生产测试使用
308512
InitializeButton();
309513
InitializeKnob();
310514
Initializespd2010Display();
@@ -352,6 +556,23 @@ class SensecapWatcher : public WifiBoard {
352556
}
353557
WifiBoard::SetPowerSaveMode(enabled);
354558
}
559+
560+
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
561+
static bool last_discharging = false;
562+
charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
563+
discharging = !charging;
564+
level = (int)BatterygetPercent(false);
565+
566+
if (discharging != last_discharging) {
567+
power_save_timer_->SetEnabled(discharging);
568+
last_discharging = discharging;
569+
}
570+
if (level <= 1 && discharging) {
571+
ESP_LOGI(TAG, "Battery level is low, shutting down");
572+
IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
573+
}
574+
return true;
575+
}
355576
};
356577

357578
DECLARE_BOARD(SensecapWatcher);

main/display/display.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ Display::~Display() {
7575
lv_obj_del(battery_label_);
7676
lv_obj_del(emotion_label_);
7777
}
78-
78+
if( low_battery_popup_ != nullptr ) {
79+
lv_obj_del(low_battery_popup_);
80+
}
7981
if (pm_lock_ != nullptr) {
8082
esp_pm_lock_delete(pm_lock_);
8183
}

main/display/display.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ class Display {
4646
lv_obj_t *battery_label_ = nullptr;
4747
lv_obj_t* chat_message_label_ = nullptr;
4848
lv_obj_t* low_battery_popup_ = nullptr;
49-
49+
lv_obj_t* low_battery_label_ = nullptr;
50+
5051
const char* battery_icon_ = nullptr;
5152
const char* network_icon_ = nullptr;
5253
bool muted_ = false;

0 commit comments

Comments
 (0)