Add dark color scheme, backlight timeout, and L/R swap feature

- Invert UI colors: black background with white text, yellow highlights
- Add 30-second backlight timeout with auto-wake on button press
- Add Swap L/R menu option with checkbox to swap audio channels
- Update volume page styling (yellow bar, full-screen container)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Brent Perteet
2025-11-21 09:08:12 -06:00
parent 31e0e3a148
commit 3bce9e772c
4 changed files with 206 additions and 27 deletions

View File

@@ -35,7 +35,7 @@
/* device name */ /* device name */
#define TARGET_DEVICE_NAME "ESP_SPEAKER" #define TARGET_DEVICE_NAME "ESP_SPEAKER"
#define LOCAL_DEVICE_NAME "ESP_A2DP_SRC" #define LOCAL_DEVICE_NAME "SOUNDSHOT"
/* AVRCP used transaction label */ /* AVRCP used transaction label */
#define APP_RC_CT_TL_GET_CAPS (0) #define APP_RC_CT_TL_GET_CAPS (0)
@@ -798,6 +798,8 @@ void generate_exp(uint8_t *buf, int len, float balance)
//float rate_hz = MIN_RATE_HZ * powf(MAX_RATE_HZ / MIN_RATE_HZ, abs_balance); //float rate_hz = MIN_RATE_HZ * powf(MAX_RATE_HZ / MIN_RATE_HZ, abs_balance);
float samples_per_click = SAMPLE_RATE / rate_hz; float samples_per_click = SAMPLE_RATE / rate_hz;
bool swap_lr = system_getSwapLR();
for (int i = 0; i < samples_needed; i++) { for (int i = 0; i < samples_needed; i++) {
int16_t left = 0; int16_t left = 0;
int16_t right = 0; int16_t right = 0;
@@ -814,8 +816,13 @@ void generate_exp(uint8_t *buf, int len, float balance)
click_timer -= 1.0f; click_timer -= 1.0f;
samples[i * 2 + 0] = left; if (swap_lr) {
samples[i * 2 + 1] = right; samples[i * 2 + 0] = right;
samples[i * 2 + 1] = left;
} else {
samples[i * 2 + 0] = left;
samples[i * 2 + 1] = right;
}
} }
} }

View File

@@ -65,6 +65,8 @@ static void createBubble(lv_obj_t * scr);
static void build_scrollable_menu(void); static void build_scrollable_menu(void);
static void currentFocusIndex(menu_context_t *ctx); static void currentFocusIndex(menu_context_t *ctx);
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text); static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text);
static lv_obj_t * addMenuItemWithCheckbox(lv_obj_t *page, const char *text, bool checked, lv_obj_t **checkbox_out);
static void update_checkbox_focus_style(lv_obj_t *cb, bool focused);
static lv_obj_t* create_menu_container(void); static lv_obj_t* create_menu_container(void);
static void show_bt_device_list(void); static void show_bt_device_list(void);
static void show_volume_control(void); static void show_volume_control(void);
@@ -103,6 +105,19 @@ static lv_obj_t *_currentPage = NULL;
static GuiMode_t _mode = GUI_BUBBLE; static GuiMode_t _mode = GUI_BUBBLE;
// Backlight timeout
#define BACKLIGHT_TIMEOUT_MS 30000
static TickType_t _lastButtonPress = 0;
static bool _backlightOn = true;
static void backlight_reset_timeout(void) {
_lastButtonPress = xTaskGetTickCount();
if (!_backlightOn) {
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
_backlightOn = true;
}
}
// Menu navigation stack // Menu navigation stack
#define MAX_MENU_STACK_SIZE 8 #define MAX_MENU_STACK_SIZE 8
typedef struct { typedef struct {
@@ -137,6 +152,9 @@ static int _current_volume = 50; // Default volume (0-100)
static lv_obj_t* _calibration_page = NULL; static lv_obj_t* _calibration_page = NULL;
static lv_timer_t* _calibration_timer = NULL; static lv_timer_t* _calibration_timer = NULL;
// Swap L/R checkbox
static lv_obj_t* _swapLR_checkbox = NULL;
// Hide *all* headers/back buttons that LVGL may show for this menu // Hide *all* headers/back buttons that LVGL may show for this menu
static void menu_hide_headers(lv_obj_t *menu) { static void menu_hide_headers(lv_obj_t *menu) {
if (!menu) return; if (!menu) return;
@@ -200,6 +218,7 @@ static lv_obj_t* create_main_page(void) {
addMenuItem(main_page, "Calibration"); addMenuItem(main_page, "Calibration");
addMenuItem(main_page, "Volume"); addMenuItem(main_page, "Volume");
addMenuItemWithCheckbox(main_page, "Swap L/R", system_getSwapLR(), &_swapLR_checkbox);
//addMenuItem(main_page, "About"); //addMenuItem(main_page, "About");
addMenuItem(main_page, "Exit"); addMenuItem(main_page, "Exit");
@@ -238,6 +257,7 @@ static void cleanup_menu(void) {
_volume_page = NULL; _volume_page = NULL;
_volume_bar = NULL; _volume_bar = NULL;
_volume_label = NULL; _volume_label = NULL;
_swapLR_checkbox = NULL;
if (_calibration_timer) { if (_calibration_timer) {
lv_timer_del(_calibration_timer); lv_timer_del(_calibration_timer);
_calibration_timer = NULL; _calibration_timer = NULL;
@@ -257,7 +277,9 @@ static void create_lvgl_demo(void)
lvgl_port_lock(0); lvgl_port_lock(0);
// Create a screen with black background // Create a screen with black background
lv_obj_t *scr = lv_scr_act(); lv_obj_t *scr = lv_scr_act();
lv_obj_set_style_bg_color(scr, lv_color_black(), 0);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
createBubble(scr); createBubble(scr);
// Menu will be created lazily when needed // Menu will be created lazily when needed
@@ -462,6 +484,14 @@ static void btn_click_cb(lv_event_t * e) {
menu_stack_push(_currentPage); menu_stack_push(_currentPage);
show_volume_control(); show_volume_control();
UNLOCK(); UNLOCK();
} else if (strcmp(txt, "Swap L/R") == 0) {
// Toggle the swap L/R state
system_toggleSwapLR();
// Update the checkbox visual (now a label showing "X" or empty)
if (_swapLR_checkbox) {
lv_label_set_text(_swapLR_checkbox, system_getSwapLR() ? "X" : "");
}
ESP_LOGI(TAG, "Swap L/R toggled to: %s", system_getSwapLR() ? "ON" : "OFF");
} else if (strcmp(txt, "Back") == 0) { } else if (strcmp(txt, "Back") == 0) {
LOCK(); LOCK();
menu_go_back(); menu_go_back();
@@ -498,18 +528,18 @@ static void refresh_highlight(void) {
if (lv_obj_has_flag(child, LV_OBJ_FLAG_USER_1)) if (lv_obj_has_flag(child, LV_OBJ_FLAG_USER_1))
{ {
lv_obj_set_style_bg_color(child, lv_color_hex(0xFF8800), 0); lv_obj_set_style_bg_color(child, lv_color_hex(0xFFFF00), 0);
lv_obj_set_style_text_color(lv_obj_get_child(child,0), lv_obj_set_style_text_color(lv_obj_get_child(child,0),
lv_color_white(), 0); lv_color_black(), 0);
selected = child; selected = child;
ESP_LOGI(TAG, "Selected"); ESP_LOGI(TAG, "Selected");
} }
else else
{ {
lv_obj_set_style_bg_color(child, lv_color_white(), 0); lv_obj_set_style_bg_color(child, lv_color_black(), 0);
lv_obj_set_style_text_color(lv_obj_get_child(child,0), lv_obj_set_style_text_color(lv_obj_get_child(child,0),
lv_color_black(), 0); lv_color_white(), 0);
} }
index++; index++;
@@ -560,6 +590,12 @@ static void menuInc(int inc)
// Update scroll on old focused item (stop scrolling) // Update scroll on old focused item (stop scrolling)
update_label_scroll(_menuContext.obj, false); update_label_scroll(_menuContext.obj, false);
// Update checkbox style on old focused item (if it has one)
lv_obj_t *old_cb = lv_obj_get_user_data(_menuContext.obj);
if (old_cb) {
update_checkbox_focus_style(old_cb, false);
}
lv_obj_add_state(next, LV_STATE_FOCUSED); lv_obj_add_state(next, LV_STATE_FOCUSED);
lv_obj_clear_state(_menuContext.obj, LV_STATE_FOCUSED); lv_obj_clear_state(_menuContext.obj, LV_STATE_FOCUSED);
lv_obj_scroll_to_view(next, LV_ANIM_ON); lv_obj_scroll_to_view(next, LV_ANIM_ON);
@@ -567,6 +603,12 @@ static void menuInc(int inc)
// Update scroll on new focused item (start scrolling) // Update scroll on new focused item (start scrolling)
update_label_scroll(next, true); update_label_scroll(next, true);
// Update checkbox style on new focused item (if it has one)
lv_obj_t *new_cb = lv_obj_get_user_data(next);
if (new_cb) {
update_checkbox_focus_style(new_cb, true);
}
_menuContext.obj = next; _menuContext.obj = next;
_menuContext.selected = search_index; _menuContext.selected = search_index;
} }
@@ -626,6 +668,83 @@ static void label_scroll_focus_cb(lv_event_t * e)
} }
} }
static lv_obj_t * addMenuItemWithCheckbox(lv_obj_t *page, const char *text, bool checked, lv_obj_t **checkbox_out)
{
if (!page || !text) {
ESP_LOGE(TAG, "Null parameters in addMenuItemWithCheckbox");
return NULL;
}
// Ensure styles are initialized
ensure_menu_styles();
lv_obj_t * btn = lv_btn_create(page);
if (!btn) {
ESP_LOGE(TAG, "Failed to create button in addMenuItemWithCheckbox");
return NULL;
}
lv_obj_set_size(btn, LV_PCT(100), ROW_H);
lv_obj_add_style(btn, &_styleUnfocusedBtn, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_add_style(btn, &_styleFocusedBtn, LV_PART_MAIN | LV_STATE_FOCUSED);
lv_obj_add_state(btn, LV_STATE_DEFAULT);
lv_obj_set_style_radius(btn, 0, LV_PART_MAIN);
lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN);
lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE);
// Label on the left (same as regular menu item)
lv_obj_t * lbl = lv_label_create(btn);
if (!lbl) {
ESP_LOGE(TAG, "Failed to create label in addMenuItemWithCheckbox");
lv_obj_del(btn);
return NULL;
}
lv_label_set_text(lbl, text);
lv_label_set_long_mode(lbl, LV_LABEL_LONG_CLIP);
lv_obj_set_width(lbl, LV_PCT(80));
lv_obj_set_style_radius(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_align(lbl, LV_ALIGN_LEFT_MID, 0, 0);
// Label on the right to act as checkbox (shows "X" when checked, empty when not)
lv_obj_t * cb = lv_label_create(btn);
lv_label_set_text(cb, checked ? "X" : "");
lv_obj_set_style_text_color(cb, lv_color_white(), 0); // Unfocused: white
lv_obj_align(cb, LV_ALIGN_RIGHT_MID, -4, 0);
if (checkbox_out) {
*checkbox_out = cb;
}
// Add focus event handler to control scrolling
lv_obj_add_event_cb(btn, label_scroll_focus_cb, LV_EVENT_ALL, NULL);
// click callback
lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
// Store checkbox in user data for focus styling updates
lv_obj_set_user_data(btn, cb);
return btn;
}
// Update checkbox label color based on parent focus state
static void update_checkbox_focus_style(lv_obj_t *cb, bool focused) {
if (!cb) return;
if (focused) {
// Focused: black text (on yellow background)
lv_obj_set_style_text_color(cb, lv_color_black(), 0);
} else {
// Unfocused: white text (on black background)
lv_obj_set_style_text_color(cb, lv_color_white(), 0);
}
}
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text) static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
{ {
if (!page || !text) { if (!page || !text) {
@@ -719,12 +838,12 @@ static void ensure_menu_styles(void) {
lv_style_init(&_styleFocusedBtn); lv_style_init(&_styleFocusedBtn);
lv_style_init(&_styleUnfocusedBtn); lv_style_init(&_styleUnfocusedBtn);
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // white bg lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0x00,0x00,0x00)); // black bg
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_black()); // black text lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_white()); // white text
//lv_style_set_text_font(&_styleUnfocusedBtn, &lv_font_unscii_16); // larger font //lv_style_set_text_font(&_styleUnfocusedBtn, &lv_font_unscii_16); // larger font
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0xFF,0xFF,0x00)); // bright yellow bg
lv_style_set_text_color(&_styleFocusedBtn, lv_color_white()); // white text lv_style_set_text_color(&_styleFocusedBtn, lv_color_black()); // black text
//lv_style_set_text_font(&_styleFocusedBtn, &lv_font_unscii_16); // larger font //lv_style_set_text_font(&_styleFocusedBtn, &lv_font_unscii_16); // larger font
styles_initialized = true; styles_initialized = true;
@@ -1066,9 +1185,9 @@ static lv_obj_t* create_bt_device_page(bool start_discovery) {
lv_label_set_text(_bt_status_item, "Devices:"); lv_label_set_text(_bt_status_item, "Devices:");
lv_obj_set_width(_bt_status_item, lv_pct(100)); lv_obj_set_width(_bt_status_item, lv_pct(100));
lv_obj_set_height(_bt_status_item, 16); // Thinner header lv_obj_set_height(_bt_status_item, 16); // Thinner header
lv_obj_set_style_bg_color(_bt_status_item, lv_color_make(0xE0, 0xE0, 0xE0), 0); lv_obj_set_style_bg_color(_bt_status_item, lv_color_make(0x00, 0x00, 0x00), 0);
lv_obj_set_style_bg_opa(_bt_status_item, LV_OPA_COVER, 0); lv_obj_set_style_bg_opa(_bt_status_item, LV_OPA_COVER, 0);
lv_obj_set_style_text_color(_bt_status_item, lv_color_black(), 0); lv_obj_set_style_text_color(_bt_status_item, lv_color_white(), 0);
lv_obj_set_style_pad_left(_bt_status_item, 5, 0); lv_obj_set_style_pad_left(_bt_status_item, 5, 0);
lv_obj_set_style_pad_top(_bt_status_item, 2, 0); lv_obj_set_style_pad_top(_bt_status_item, 2, 0);
lv_obj_set_style_text_align(_bt_status_item, LV_TEXT_ALIGN_LEFT, 0); lv_obj_set_style_text_align(_bt_status_item, LV_TEXT_ALIGN_LEFT, 0);
@@ -1086,7 +1205,7 @@ static lv_obj_t* create_bt_device_page(bool start_discovery) {
lv_obj_set_style_outline_width(_bt_device_container, 0, LV_PART_MAIN); lv_obj_set_style_outline_width(_bt_device_container, 0, LV_PART_MAIN);
lv_obj_set_style_pad_all(_bt_device_container, 0, LV_PART_MAIN); lv_obj_set_style_pad_all(_bt_device_container, 0, LV_PART_MAIN);
lv_obj_set_style_pad_row(_bt_device_container, 0, LV_PART_MAIN); lv_obj_set_style_pad_row(_bt_device_container, 0, LV_PART_MAIN);
lv_obj_set_style_bg_color(_bt_device_container, lv_color_white(), LV_PART_MAIN); lv_obj_set_style_bg_color(_bt_device_container, lv_color_black(), LV_PART_MAIN);
lv_obj_set_style_bg_opa(_bt_device_container, LV_OPA_COVER, LV_PART_MAIN); lv_obj_set_style_bg_opa(_bt_device_container, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_flex_flow(_bt_device_container, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(_bt_device_container, LV_FLEX_FLOW_COLUMN);
lv_obj_set_scrollbar_mode(_bt_device_container, LV_SCROLLBAR_MODE_AUTO); lv_obj_set_scrollbar_mode(_bt_device_container, LV_SCROLLBAR_MODE_AUTO);
@@ -1235,9 +1354,11 @@ static lv_obj_t* create_volume_page(void) {
// Create a container to center everything vertically // Create a container to center everything vertically
lv_obj_t* container = lv_obj_create(_volume_page); lv_obj_t* container = lv_obj_create(_volume_page);
lv_obj_set_size(container, lv_pct(100), LV_SIZE_CONTENT); lv_obj_set_size(container, lv_pct(100), lv_pct(100));
lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0); lv_obj_set_style_bg_color(container, lv_color_black(), 0);
lv_obj_set_style_bg_opa(container, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(container, 0, 0); lv_obj_set_style_border_width(container, 0, 0);
lv_obj_set_style_radius(container, 0, 0);
lv_obj_set_style_pad_all(container, 0, 0); lv_obj_set_style_pad_all(container, 0, 0);
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
@@ -1247,7 +1368,7 @@ static lv_obj_t* create_volume_page(void) {
lv_obj_t* title = lv_label_create(container); lv_obj_t* title = lv_label_create(container);
lv_label_set_text(title, "Volume Control"); lv_label_set_text(title, "Volume Control");
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0); lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_style_text_color(title, lv_color_black(), 0); lv_obj_set_style_text_color(title, lv_color_white(), 0);
lv_obj_set_style_pad_bottom(title, 8, 0); lv_obj_set_style_pad_bottom(title, 8, 0);
// Create volume bar (progress bar) // Create volume bar (progress bar)
@@ -1255,6 +1376,8 @@ static lv_obj_t* create_volume_page(void) {
lv_obj_set_size(_volume_bar, 120, 20); lv_obj_set_size(_volume_bar, 120, 20);
lv_bar_set_range(_volume_bar, 0, 100); lv_bar_set_range(_volume_bar, 0, 100);
lv_bar_set_value(_volume_bar, _current_volume, LV_ANIM_OFF); lv_bar_set_value(_volume_bar, _current_volume, LV_ANIM_OFF);
lv_obj_set_style_bg_color(_volume_bar, lv_color_make(0x40, 0x40, 0x40), LV_PART_MAIN); // Dark gray background
lv_obj_set_style_bg_color(_volume_bar, lv_color_make(0xFF, 0xFF, 0x00), LV_PART_INDICATOR); // Yellow indicator
lv_obj_set_style_pad_top(_volume_bar, 5, 0); lv_obj_set_style_pad_top(_volume_bar, 5, 0);
lv_obj_set_style_pad_bottom(_volume_bar, 5, 0); lv_obj_set_style_pad_bottom(_volume_bar, 5, 0);
@@ -1264,6 +1387,7 @@ static lv_obj_t* create_volume_page(void) {
snprintf(volume_text, sizeof(volume_text), "%d%%", _current_volume); snprintf(volume_text, sizeof(volume_text), "%d%%", _current_volume);
lv_label_set_text(_volume_label, volume_text); lv_label_set_text(_volume_label, volume_text);
lv_obj_set_style_text_align(_volume_label, LV_TEXT_ALIGN_CENTER, 0); lv_obj_set_style_text_align(_volume_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_style_text_color(_volume_label, lv_color_white(), 0);
lv_obj_set_style_pad_top(_volume_label, 5, 0); lv_obj_set_style_pad_top(_volume_label, 5, 0);
return _volume_page; return _volume_page;
@@ -1438,7 +1562,7 @@ static void calibration_click_cb(lv_event_t * e) {
// Show "Calibrated" message centered // Show "Calibrated" message centered
lv_obj_t* label = lv_label_create(container); lv_obj_t* label = lv_label_create(container);
lv_label_set_text(label, "Calibrated"); lv_label_set_text(label, "Calibrated");
lv_obj_set_style_text_color(label, lv_color_black(), 0); lv_obj_set_style_text_color(label, lv_color_white(), 0);
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0); lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
lv_obj_center(label); lv_obj_center(label);
} }
@@ -1467,7 +1591,7 @@ static void calibration_click_cb(lv_event_t * e) {
// Show "Cleared" message centered // Show "Cleared" message centered
lv_obj_t* label = lv_label_create(container); lv_obj_t* label = lv_label_create(container);
lv_label_set_text(label, "Cleared"); lv_label_set_text(label, "Cleared");
lv_obj_set_style_text_color(label, lv_color_black(), 0); lv_obj_set_style_text_color(label, lv_color_white(), 0);
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0); lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
lv_obj_center(label); lv_obj_center(label);
} }
@@ -1556,11 +1680,11 @@ static void build_scrollable_menu(void) {
lv_style_init(&_styleFocusedBtn); lv_style_init(&_styleFocusedBtn);
lv_style_init(&_styleUnfocusedBtn); lv_style_init(&_styleUnfocusedBtn);
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // gray bg lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0x00,0x00,0x00)); // black bg
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_hex(0xFF8800)); lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_white());
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0xFF,0xFF,0x00)); // bright yellow bg
lv_style_set_text_color(&_styleUnfocusedBtn,lv_color_black()); lv_style_set_text_color(&_styleFocusedBtn, lv_color_black());
// 2) Inside it, create the lv_menu and hide its sidebar/header // 2) Inside it, create the lv_menu and hide its sidebar/header
@@ -1653,13 +1777,28 @@ static void gui_task(void *pvParameters)
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
UNLOCK(); UNLOCK();
// Initialize backlight timeout
_lastButtonPress = xTaskGetTickCount();
_backlightOn = true;
ESP_LOGI(TAG, "Start GUI Task..."); ESP_LOGI(TAG, "Start GUI Task...");
while (1) while (1)
{ {
// Check backlight timeout
if (_backlightOn) {
TickType_t elapsed = xTaskGetTickCount() - _lastButtonPress;
if (elapsed > pdMS_TO_TICKS(BACKLIGHT_TIMEOUT_MS)) {
gpio_set_level(PIN_NUM_BK_LIGHT, 0);
_backlightOn = false;
ESP_LOGI(TAG, "Backlight off due to timeout");
}
}
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE) if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
{ {
// Reset backlight timeout on any button press
backlight_reset_timeout();
switch (ev) { switch (ev) {
#if 0 #if 0
case (KEY_UP << KEY_LONG_PRESS): case (KEY_UP << KEY_LONG_PRESS):

View File

@@ -23,6 +23,7 @@ void system_init(void)
_systemState.primaryAxis = PRIMARY_AXIS; _systemState.primaryAxis = PRIMARY_AXIS;
_systemState.pairedDeviceCount = 0; _systemState.pairedDeviceCount = 0;
_systemState.isCharging = false; _systemState.isCharging = false;
_systemState.swapLR = false;
_systemEvent = xEventGroupCreate(); _systemEvent = xEventGroupCreate();
@@ -74,6 +75,31 @@ bool system_getChargeStatus(void)
return charging; return charging;
} }
void system_setSwapLR(bool swap)
{
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.swapLR = swap;
xSemaphoreGive(_eventManager.mutex);
ESP_LOGI("system", "Swap L/R: %s", swap ? "ON" : "OFF");
}
bool system_getSwapLR(void)
{
bool swap;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
swap = _systemState.swapLR;
xSemaphoreGive(_eventManager.mutex);
return swap;
}
void system_toggleSwapLR(void)
{
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.swapLR = !_systemState.swapLR;
ESP_LOGI("system", "Swap L/R toggled: %s", _systemState.swapLR ? "ON" : "OFF");
xSemaphoreGive(_eventManager.mutex);
}
void system_setZeroAngle(void) void system_setZeroAngle(void)
{ {
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY); xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);

View File

@@ -55,6 +55,9 @@ typedef struct SystemState_s
// Charge status // Charge status
bool isCharging; bool isCharging;
// Swap L/R audio channels
bool swapLR;
} SystemState_t; } SystemState_t;
@@ -90,6 +93,10 @@ float system_getAngle(void);
void system_setChargeStatus(bool charging); void system_setChargeStatus(bool charging);
bool system_getChargeStatus(void); bool system_getChargeStatus(void);
void system_setSwapLR(bool swap);
bool system_getSwapLR(void);
void system_toggleSwapLR(void);
void system_setZeroAngle(void); void system_setZeroAngle(void);
void system_clearZeroAngle(void); void system_clearZeroAngle(void);
float system_getZeroAngle(void); float system_getZeroAngle(void);