25
25
#include < iot_knob.h>
26
26
#include < esp_io_expander_tca95xx_16bit.h>
27
27
#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"
28
33
29
34
#define TAG " sensecap_watcher"
30
35
31
36
32
37
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
+ };
34
86
35
87
class SensecapWatcher : public WifiBoard {
36
88
private:
@@ -42,7 +94,7 @@ class SensecapWatcher : public WifiBoard {
42
94
PowerSaveTimer* power_save_timer_;
43
95
esp_lcd_panel_io_handle_t panel_io_ = nullptr ;
44
96
esp_lcd_panel_handle_t panel_ = nullptr ;
45
-
97
+ uint32_t long_press_cnt_;
46
98
void InitializePowerSaveTimer () {
47
99
power_save_timer_ = new PowerSaveTimer (-1 , 60 , 300 );
48
100
power_save_timer_->OnEnterSleepMode ([this ]() {
@@ -144,7 +196,7 @@ class SensecapWatcher : public WifiBoard {
144
196
void OnKnobRotate (bool clockwise) {
145
197
auto codec = GetAudioCodec ();
146
198
int current_volume = codec->output_volume ();
147
- int new_volume = current_volume + (clockwise ? 5 : - 5 );
199
+ int new_volume = current_volume + (clockwise ? - 5 : 5 );
148
200
149
201
// 确保音量在有效范围内
150
202
if (new_volume > 100 ) {
@@ -163,7 +215,7 @@ class SensecapWatcher : public WifiBoard {
163
215
ESP_LOGE (TAG, " Failed to set volume! Expected:%d Actual:%d" ,
164
216
new_volume, codec->output_volume ());
165
217
}
166
- GetDisplay ()->ShowNotification (" 音量: " + std::to_string (codec->output_volume ()));
218
+ GetDisplay ()->ShowNotification (std::string (Lang::Strings::VOLUME) + " : " + std::to_string (codec->output_volume ()));
167
219
power_save_timer_->WakeUp ();
168
220
}
169
221
@@ -193,7 +245,7 @@ class SensecapWatcher : public WifiBoard {
193
245
},
194
246
};
195
247
196
- // watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击
248
+ // watcher 是通过长按滚轮进行开机的, 需要等待滚轮释放, 否则用户开机松手时可能会误触成单击
197
249
ESP_LOGI (TAG, " waiting for knob button release" );
198
250
while (IoExpanderGetLevel (BSP_KNOB_BTN) == 0 ) {
199
251
vTaskDelay (50 / portTICK_PERIOD_MS);
@@ -212,13 +264,26 @@ class SensecapWatcher : public WifiBoard {
212
264
iot_button_register_cb (btns, BUTTON_LONG_PRESS_START, [](void * button_handle, void * usr_data) {
213
265
auto self = static_cast <SensecapWatcher*>(usr_data);
214
266
bool is_charging = (self->IoExpanderGetLevel (BSP_PWR_VBUS_IN_DET) == 0 );
267
+ self->long_press_cnt_ = 0 ;
215
268
if (is_charging) {
216
269
ESP_LOGI (TAG, " charging" );
217
270
} else {
218
271
self->IoExpanderSetLevel (BSP_PWR_LCD, 0 );
219
272
self->IoExpanderSetLevel (BSP_PWR_SYSTEM, 0 );
220
273
}
221
274
}, 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
+
222
287
}
223
288
224
289
void InitializeSpi () {
@@ -270,23 +335,18 @@ class SensecapWatcher : public WifiBoard {
270
335
esp_lcd_panel_mirror (panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
271
336
esp_lcd_panel_disp_on_off (panel_, true );
272
337
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);
280
340
281
341
// 使每次刷新的起始列数索引是4的倍数且列数总数是4的倍数,以满足SPD2010的要求
282
342
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 ;
290
350
}, LV_EVENT_INVALIDATE_AREA, NULL );
291
351
292
352
}
@@ -296,15 +356,159 @@ class SensecapWatcher : public WifiBoard {
296
356
auto & thing_manager = iot::ThingManager::GetInstance ();
297
357
thing_manager.AddThing (iot::CreateThing (" Speaker" ));
298
358
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));
299
502
}
300
503
301
504
public:
302
- SensecapWatcher (){
505
+ SensecapWatcher () {
303
506
ESP_LOGI (TAG, " Initialize Sensecap Watcher" );
304
507
InitializePowerSaveTimer ();
305
508
InitializeI2c ();
306
509
InitializeSpi ();
307
510
InitializeExpander ();
511
+ InitializeCmd (); // 工厂生产测试使用
308
512
InitializeButton ();
309
513
InitializeKnob ();
310
514
Initializespd2010Display ();
@@ -352,6 +556,23 @@ class SensecapWatcher : public WifiBoard {
352
556
}
353
557
WifiBoard::SetPowerSaveMode (enabled);
354
558
}
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
+ }
355
576
};
356
577
357
578
DECLARE_BOARD (SensecapWatcher);
0 commit comments