Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d427859804 | |||
| 40bea065a7 | |||
|
|
3bce9e772c |
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"C_Cpp.intelliSenseEngine": "default",
|
||||
"idf.projectName": "soundshot",
|
||||
"idf.espIdfPathWin": "C:\\Users\\Brent.Perteet\\esp\\v5.4.1\\esp-idf",
|
||||
"idf.openOcdConfigs": [
|
||||
"board/esp32-wrover-kit-3.3v.cfg"
|
||||
],
|
||||
"idf.portWin": "COM14",
|
||||
"idf.portWin": "COM3",
|
||||
"idf.toolsPathWin": "C:\\Users\\Brent.Perteet\\.espressif",
|
||||
"idf.flashType": "UART",
|
||||
"files.associations": {
|
||||
@@ -37,7 +38,8 @@
|
||||
"lv_slider.h": "c",
|
||||
"lv_menu.h": "c",
|
||||
"stdint.h": "c",
|
||||
"random": "c"
|
||||
"random": "c",
|
||||
"string.h": "c"
|
||||
},
|
||||
"git.ignoreLimitWarning": true,
|
||||
"idf.pythonInstallPath": "C:\\Users\\Brent.Perteet\\.espressif\\tools\\idf-python\\3.11.2\\python.exe",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
idf_component_register(SRCS "bt_app.c" "system.c" "bubble.c" "keypad.c" "main.c"
|
||||
idf_component_register(SRCS "battery.c" "bt_app.c" "system.c" "bubble.c" "keypad.c" "main.c"
|
||||
"gui.c"
|
||||
"lsm6dsv.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES "driver"
|
||||
"esp_lcd"
|
||||
"lvgl"
|
||||
REQUIRES "driver"
|
||||
"esp_lcd"
|
||||
"lvgl"
|
||||
"esp_lvgl_port"
|
||||
"esp_timer"
|
||||
"nvs_flash"
|
||||
"bt")
|
||||
"bt"
|
||||
"esp_adc")
|
||||
|
||||
|
||||
271
main/battery.c
Normal file
271
main/battery.c
Normal file
@@ -0,0 +1,271 @@
|
||||
#include "battery.h"
|
||||
#include "system.h"
|
||||
#include "gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
static const char *TAG = "battery";
|
||||
|
||||
static adc_oneshot_unit_handle_t adc1_handle = NULL;
|
||||
static adc_cali_handle_t adc1_cali_handle = NULL;
|
||||
static bool adc_calibration_enabled = false;
|
||||
|
||||
// ADC Calibration initialization
|
||||
static bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
|
||||
{
|
||||
adc_cali_handle_t handle = NULL;
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
bool calibrated = false;
|
||||
|
||||
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
||||
if (!calibrated) {
|
||||
ESP_LOGI(TAG, "Calibration scheme: Curve Fitting");
|
||||
adc_cali_curve_fitting_config_t cali_config = {
|
||||
.unit_id = unit,
|
||||
.chan = channel,
|
||||
.atten = atten,
|
||||
.bitwidth = BATTERY_ADC_WIDTH,
|
||||
};
|
||||
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
||||
if (ret == ESP_OK) {
|
||||
calibrated = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
|
||||
if (!calibrated) {
|
||||
ESP_LOGI(TAG, "Calibration scheme: Line Fitting");
|
||||
adc_cali_line_fitting_config_t cali_config = {
|
||||
.unit_id = unit,
|
||||
.atten = atten,
|
||||
.bitwidth = BATTERY_ADC_WIDTH,
|
||||
};
|
||||
ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
||||
if (ret == ESP_OK) {
|
||||
calibrated = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
*out_handle = handle;
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "ADC calibration successful");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "ADC calibration failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
return calibrated;
|
||||
}
|
||||
|
||||
esp_err_t battery_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
// Configure ADC1 oneshot mode
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
ret = adc_oneshot_new_unit(&init_config, &adc1_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize ADC unit: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Configure ADC channel
|
||||
adc_oneshot_chan_cfg_t config = {
|
||||
.bitwidth = BATTERY_ADC_WIDTH,
|
||||
.atten = BATTERY_ADC_ATTEN,
|
||||
};
|
||||
ret = adc_oneshot_config_channel(adc1_handle, BATTERY_ADC_CHANNEL, &config);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure ADC channel: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialize calibration
|
||||
adc_calibration_enabled = adc_calibration_init(ADC_UNIT_1, BATTERY_ADC_CHANNEL,
|
||||
BATTERY_ADC_ATTEN, &adc1_cali_handle);
|
||||
|
||||
ESP_LOGI(TAG, "Battery monitoring initialized on GPIO34 (ADC1_CH6)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int battery_read_raw(void)
|
||||
{
|
||||
int adc_raw = 0;
|
||||
esp_err_t ret = adc_oneshot_read(adc1_handle, BATTERY_ADC_CHANNEL, &adc_raw);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read ADC: %s", esp_err_to_name(ret));
|
||||
return -1;
|
||||
}
|
||||
return adc_raw;
|
||||
}
|
||||
|
||||
int battery_read_voltage_mv(void)
|
||||
{
|
||||
int voltage_mv = 0;
|
||||
int adc_sum = 0;
|
||||
int valid_samples = 0;
|
||||
|
||||
// Take multiple samples and average
|
||||
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
||||
int adc_raw = battery_read_raw();
|
||||
if (adc_raw >= 0) {
|
||||
adc_sum += adc_raw;
|
||||
valid_samples++;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1)); // Small delay between samples
|
||||
}
|
||||
|
||||
if (valid_samples == 0) {
|
||||
ESP_LOGE(TAG, "No valid ADC samples");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int adc_avg = adc_sum / valid_samples;
|
||||
|
||||
// Convert to voltage using calibration if available
|
||||
if (adc_calibration_enabled) {
|
||||
esp_err_t ret = adc_cali_raw_to_voltage(adc1_cali_handle, adc_avg, &voltage_mv);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Calibration conversion failed, using raw calculation");
|
||||
adc_calibration_enabled = false; // Disable for future reads
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to manual calculation if calibration not available
|
||||
if (!adc_calibration_enabled) {
|
||||
// Simple linear conversion for 12-bit ADC with 12dB attenuation
|
||||
// Approximate range: 0-3300mV for 0-4095 raw values
|
||||
voltage_mv = (adc_avg * 3300) / 4095;
|
||||
}
|
||||
|
||||
// Apply voltage divider ratio to get actual battery voltage
|
||||
voltage_mv = (int)(voltage_mv * BATTERY_VOLTAGE_DIVIDER_RATIO);
|
||||
|
||||
return voltage_mv;
|
||||
}
|
||||
|
||||
int battery_get_percentage(void)
|
||||
{
|
||||
int voltage_mv = battery_read_voltage_mv();
|
||||
|
||||
if (voltage_mv < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clamp to min/max range
|
||||
if (voltage_mv >= BATTERY_VOLTAGE_MAX) {
|
||||
return 100;
|
||||
}
|
||||
if (voltage_mv <= BATTERY_VOLTAGE_MIN) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Linear interpolation between min and max
|
||||
int percentage = ((voltage_mv - BATTERY_VOLTAGE_MIN) * 100) /
|
||||
(BATTERY_VOLTAGE_MAX - BATTERY_VOLTAGE_MIN);
|
||||
|
||||
return percentage;
|
||||
}
|
||||
|
||||
// Battery monitoring task
|
||||
static void battery_monitoring_task(void *pvParameters)
|
||||
{
|
||||
ESP_LOGI(TAG, "Battery monitoring task started");
|
||||
bool led_toggle = false;
|
||||
int loop_count = 0;
|
||||
int voltage_mv = 0;
|
||||
bool is_charging = false;
|
||||
|
||||
while (1)
|
||||
{
|
||||
// Read voltage every 20 iterations (5000ms)
|
||||
if (loop_count % 20 == 0) {
|
||||
voltage_mv = battery_read_voltage_mv();
|
||||
int percentage = battery_get_percentage();
|
||||
|
||||
if (voltage_mv >= 0 && percentage >= 0) {
|
||||
// Update system state with battery info
|
||||
system_setBatteryVoltage(voltage_mv);
|
||||
system_setBatteryPercentage(percentage);
|
||||
|
||||
ESP_LOGI(TAG, "Battery: %d mV (%d%%)", voltage_mv, percentage);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to read battery voltage");
|
||||
}
|
||||
|
||||
// Read charge status and update system
|
||||
is_charging = !gpio_get_level(PIN_NUM_CHARGE_STATUS);
|
||||
system_setChargeStatus(is_charging);
|
||||
}
|
||||
|
||||
// LED control based on charging status and voltage (runs every 250ms)
|
||||
if (is_charging)
|
||||
{
|
||||
// Charging: Red until voltage > 4150mV, then Green
|
||||
if (voltage_mv > 4150) {
|
||||
gpio_set_level(PIN_LED_GREEN, 1); // Green ON
|
||||
gpio_set_level(PIN_LED_RED, 0); // Red OFF
|
||||
} else {
|
||||
gpio_set_level(PIN_LED_GREEN, 0); // Green OFF
|
||||
gpio_set_level(PIN_LED_RED, 1); // Red ON
|
||||
}
|
||||
gpio_set_level(PIN_NUM_LED_2, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not charging: voltage-based indication
|
||||
if (voltage_mv > 3900)
|
||||
{
|
||||
// Good voltage: Green steady
|
||||
gpio_set_level(PIN_LED_GREEN, 1); // Green ON
|
||||
gpio_set_level(PIN_LED_RED, 0); // Red OFF
|
||||
}
|
||||
else
|
||||
if (voltage_mv > 3700)
|
||||
{
|
||||
// Warning voltage: Green flashing at 2Hz
|
||||
gpio_set_level(PIN_LED_GREEN, led_toggle ? 0 : 1);
|
||||
gpio_set_level(PIN_LED_RED, 0); // Red OFF
|
||||
led_toggle = !led_toggle;
|
||||
}
|
||||
else
|
||||
if (voltage_mv > 3600)
|
||||
{
|
||||
// Low voltage: Red steady
|
||||
gpio_set_level(PIN_LED_GREEN, 0); // Green OFF
|
||||
gpio_set_level(PIN_LED_RED, 1); // Red ON
|
||||
}
|
||||
else
|
||||
if (voltage_mv > 3500)
|
||||
{
|
||||
// Warning voltage: Red flashing at 2Hz
|
||||
gpio_set_level(PIN_LED_RED, led_toggle ? 0 : 1);
|
||||
gpio_set_level(PIN_LED_GREEN, 0); // Green OFF
|
||||
led_toggle = !led_toggle;
|
||||
}
|
||||
else
|
||||
{
|
||||
// power off
|
||||
gpio_set_level(PIN_NUM_nON, 0);
|
||||
}
|
||||
|
||||
gpio_set_level(PIN_NUM_LED_2, 1);
|
||||
}
|
||||
|
||||
loop_count++;
|
||||
vTaskDelay(pdMS_TO_TICKS(250));
|
||||
}
|
||||
}
|
||||
|
||||
void battery_start_monitoring_task(void)
|
||||
{
|
||||
xTaskCreate(battery_monitoring_task, "battery_task", 3072, NULL, 5, NULL);
|
||||
ESP_LOGI(TAG, "Battery monitoring task created");
|
||||
}
|
||||
56
main/battery.h
Normal file
56
main/battery.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef BATTERY_H
|
||||
#define BATTERY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
// Battery monitoring configuration
|
||||
#define BATTERY_ADC_CHANNEL ADC_CHANNEL_6 // GPIO34 (ADC1_CH6)
|
||||
#define BATTERY_ADC_ATTEN ADC_ATTEN_DB_12 // Full range ~3.9V (0-3300mV with attenuation)
|
||||
#define BATTERY_ADC_WIDTH ADC_BITWIDTH_12 // 12-bit resolution (0-4095)
|
||||
|
||||
// Battery voltage calculation constants
|
||||
// Adjust these based on your voltage divider circuit
|
||||
#define BATTERY_VOLTAGE_DIVIDER_RATIO 2.0f // Example: R1=R2, adjust for your circuit
|
||||
#define BATTERY_SAMPLES 16 // Number of samples to average
|
||||
|
||||
// Battery percentage thresholds (in millivolts at battery)
|
||||
#define BATTERY_VOLTAGE_MAX 4200 // Fully charged Li-Ion
|
||||
#define BATTERY_VOLTAGE_MIN 3000 // Empty Li-Ion (cutoff)
|
||||
|
||||
/**
|
||||
* @brief Initialize battery monitoring ADC
|
||||
*
|
||||
* @return esp_err_t ESP_OK on success
|
||||
*/
|
||||
esp_err_t battery_init(void);
|
||||
|
||||
/**
|
||||
* @brief Read raw ADC value from battery pin
|
||||
*
|
||||
* @return int Raw ADC value (0-4095)
|
||||
*/
|
||||
int battery_read_raw(void);
|
||||
|
||||
/**
|
||||
* @brief Read battery voltage in millivolts (averaged)
|
||||
*
|
||||
* @return int Battery voltage in mV
|
||||
*/
|
||||
int battery_read_voltage_mv(void);
|
||||
|
||||
/**
|
||||
* @brief Get battery percentage (0-100)
|
||||
*
|
||||
* @return int Battery percentage
|
||||
*/
|
||||
int battery_get_percentage(void);
|
||||
|
||||
/**
|
||||
* @brief Start battery monitoring task
|
||||
*
|
||||
* This task periodically reads battery voltage and updates system state
|
||||
*/
|
||||
void battery_start_monitoring_task(void);
|
||||
|
||||
#endif // BATTERY_H
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
/* device name */
|
||||
#define TARGET_DEVICE_NAME "ESP_SPEAKER"
|
||||
#define LOCAL_DEVICE_NAME "ESP_A2DP_SRC"
|
||||
#define LOCAL_DEVICE_NAME "SOUNDSHOT"
|
||||
|
||||
/* AVRCP used transaction label */
|
||||
#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 samples_per_click = SAMPLE_RATE / rate_hz;
|
||||
|
||||
bool swap_lr = system_getSwapLR();
|
||||
|
||||
for (int i = 0; i < samples_needed; i++) {
|
||||
int16_t left = 0;
|
||||
int16_t right = 0;
|
||||
@@ -814,8 +816,13 @@ void generate_exp(uint8_t *buf, int len, float balance)
|
||||
|
||||
click_timer -= 1.0f;
|
||||
|
||||
samples[i * 2 + 0] = left;
|
||||
samples[i * 2 + 1] = right;
|
||||
if (swap_lr) {
|
||||
samples[i * 2 + 0] = right;
|
||||
samples[i * 2 + 1] = left;
|
||||
} else {
|
||||
samples[i * 2 + 0] = left;
|
||||
samples[i * 2 + 1] = right;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#define PIN_NUM_nON 26
|
||||
#define PIN_NUM_CHARGE_STATUS 2
|
||||
|
||||
#define PIN_LED_RED PIN_NUM_LED_0
|
||||
#define PIN_LED_GREEN PIN_NUM_LED_1
|
||||
|
||||
|
||||
|
||||
// Define GPIO pins
|
||||
#ifdef DEVKIT
|
||||
|
||||
258
main/gui.c
258
main/gui.c
@@ -65,6 +65,8 @@ static void createBubble(lv_obj_t * scr);
|
||||
static void build_scrollable_menu(void);
|
||||
static void currentFocusIndex(menu_context_t *ctx);
|
||||
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 void show_bt_device_list(void);
|
||||
static void show_volume_control(void);
|
||||
@@ -98,11 +100,25 @@ static lv_obj_t *list;
|
||||
static lv_obj_t *buttons[MAX_ITEMS];
|
||||
static int selected_index = 0;
|
||||
static lv_obj_t *_bubble = NULL;
|
||||
static lv_obj_t *_voltageLabel = NULL;
|
||||
static lv_obj_t * _menu = NULL;
|
||||
static lv_obj_t *_currentPage = NULL;
|
||||
|
||||
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
|
||||
#define MAX_MENU_STACK_SIZE 8
|
||||
typedef struct {
|
||||
@@ -137,6 +153,9 @@ static int _current_volume = 50; // Default volume (0-100)
|
||||
static lv_obj_t* _calibration_page = 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
|
||||
static void menu_hide_headers(lv_obj_t *menu) {
|
||||
if (!menu) return;
|
||||
@@ -200,6 +219,7 @@ static lv_obj_t* create_main_page(void) {
|
||||
|
||||
addMenuItem(main_page, "Calibration");
|
||||
addMenuItem(main_page, "Volume");
|
||||
addMenuItemWithCheckbox(main_page, "Swap L/R", system_getSwapLR(), &_swapLR_checkbox);
|
||||
//addMenuItem(main_page, "About");
|
||||
addMenuItem(main_page, "Exit");
|
||||
|
||||
@@ -223,6 +243,9 @@ static void show_menu(void) {
|
||||
|
||||
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
if (_voltageLabel != NULL) {
|
||||
lv_obj_add_flag(_voltageLabel, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup_menu(void) {
|
||||
@@ -238,6 +261,7 @@ static void cleanup_menu(void) {
|
||||
_volume_page = NULL;
|
||||
_volume_bar = NULL;
|
||||
_volume_label = NULL;
|
||||
_swapLR_checkbox = NULL;
|
||||
if (_calibration_timer) {
|
||||
lv_timer_del(_calibration_timer);
|
||||
_calibration_timer = NULL;
|
||||
@@ -249,6 +273,9 @@ static void cleanup_menu(void) {
|
||||
static void show_bubble(void) {
|
||||
cleanup_menu(); // Free menu memory when returning to bubble
|
||||
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
if (_voltageLabel != NULL) {
|
||||
lv_obj_remove_flag(_voltageLabel, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
static void create_lvgl_demo(void)
|
||||
@@ -257,7 +284,9 @@ static void create_lvgl_demo(void)
|
||||
lvgl_port_lock(0);
|
||||
// Create a screen with black background
|
||||
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);
|
||||
// Menu will be created lazily when needed
|
||||
|
||||
@@ -371,6 +400,23 @@ static void createBubble(lv_obj_t * scr)
|
||||
// (usually on the next tick, or immediately if LVGL is idle).
|
||||
|
||||
_bubble = level;
|
||||
|
||||
#if 0
|
||||
// Create voltage label at the top
|
||||
_voltageLabel = lv_label_create(scr);
|
||||
lv_label_set_text(_voltageLabel, "---- mV");
|
||||
lv_obj_align(_voltageLabel, LV_ALIGN_TOP_MID, 0, 10);
|
||||
lv_obj_set_style_text_font(_voltageLabel, &lv_font_montserrat_14, 0);
|
||||
// Add white background for contrast
|
||||
lv_obj_set_style_bg_color(_voltageLabel, lv_color_hex(0xFFFFFF), 0);
|
||||
lv_obj_set_style_bg_opa(_voltageLabel, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_text_color(_voltageLabel, lv_color_hex(0x000000), 0);
|
||||
lv_obj_set_style_pad_left(_voltageLabel, 8, 0);
|
||||
lv_obj_set_style_pad_right(_voltageLabel, 8, 0);
|
||||
lv_obj_set_style_pad_top(_voltageLabel, 4, 0);
|
||||
lv_obj_set_style_pad_bottom(_voltageLabel, 4, 0);
|
||||
lv_obj_set_style_radius(_voltageLabel, 4, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -462,6 +508,14 @@ static void btn_click_cb(lv_event_t * e) {
|
||||
menu_stack_push(_currentPage);
|
||||
show_volume_control();
|
||||
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) {
|
||||
LOCK();
|
||||
menu_go_back();
|
||||
@@ -498,18 +552,18 @@ static void refresh_highlight(void) {
|
||||
|
||||
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_color_white(), 0);
|
||||
lv_color_black(), 0);
|
||||
|
||||
selected = child;
|
||||
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_color_black(), 0);
|
||||
lv_color_white(), 0);
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -560,6 +614,12 @@ static void menuInc(int inc)
|
||||
// Update scroll on old focused item (stop scrolling)
|
||||
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_clear_state(_menuContext.obj, LV_STATE_FOCUSED);
|
||||
lv_obj_scroll_to_view(next, LV_ANIM_ON);
|
||||
@@ -567,6 +627,12 @@ static void menuInc(int inc)
|
||||
// Update scroll on new focused item (start scrolling)
|
||||
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.selected = search_index;
|
||||
}
|
||||
@@ -626,6 +692,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)
|
||||
{
|
||||
if (!page || !text) {
|
||||
@@ -719,12 +862,12 @@ static void ensure_menu_styles(void) {
|
||||
lv_style_init(&_styleFocusedBtn);
|
||||
lv_style_init(&_styleUnfocusedBtn);
|
||||
|
||||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // white bg
|
||||
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_black()); // black text
|
||||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0x00,0x00,0x00)); // black bg
|
||||
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_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg
|
||||
lv_style_set_text_color(&_styleFocusedBtn, lv_color_white()); // white text
|
||||
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0xFF,0xFF,0x00)); // bright yellow bg
|
||||
lv_style_set_text_color(&_styleFocusedBtn, lv_color_black()); // black text
|
||||
//lv_style_set_text_font(&_styleFocusedBtn, &lv_font_unscii_16); // larger font
|
||||
|
||||
styles_initialized = true;
|
||||
@@ -1066,9 +1209,9 @@ static lv_obj_t* create_bt_device_page(bool start_discovery) {
|
||||
lv_label_set_text(_bt_status_item, "Devices:");
|
||||
lv_obj_set_width(_bt_status_item, lv_pct(100));
|
||||
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_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_top(_bt_status_item, 2, 0);
|
||||
lv_obj_set_style_text_align(_bt_status_item, LV_TEXT_ALIGN_LEFT, 0);
|
||||
@@ -1086,7 +1229,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_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_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_flex_flow(_bt_device_container, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_scrollbar_mode(_bt_device_container, LV_SCROLLBAR_MODE_AUTO);
|
||||
@@ -1170,6 +1313,9 @@ static void show_bt_device_list(void) {
|
||||
|
||||
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
if (_voltageLabel != NULL) {
|
||||
lv_obj_add_flag(_voltageLabel, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1235,9 +1381,11 @@ static lv_obj_t* create_volume_page(void) {
|
||||
|
||||
// Create a container to center everything vertically
|
||||
lv_obj_t* container = lv_obj_create(_volume_page);
|
||||
lv_obj_set_size(container, lv_pct(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_size(container, lv_pct(100), lv_pct(100));
|
||||
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_radius(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_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
@@ -1247,7 +1395,7 @@ static lv_obj_t* create_volume_page(void) {
|
||||
lv_obj_t* title = lv_label_create(container);
|
||||
lv_label_set_text(title, "Volume Control");
|
||||
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);
|
||||
|
||||
// Create volume bar (progress bar)
|
||||
@@ -1255,6 +1403,8 @@ static lv_obj_t* create_volume_page(void) {
|
||||
lv_obj_set_size(_volume_bar, 120, 20);
|
||||
lv_bar_set_range(_volume_bar, 0, 100);
|
||||
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_bottom(_volume_bar, 5, 0);
|
||||
|
||||
@@ -1264,6 +1414,7 @@ static lv_obj_t* create_volume_page(void) {
|
||||
snprintf(volume_text, sizeof(volume_text), "%d%%", _current_volume);
|
||||
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_color(_volume_label, lv_color_white(), 0);
|
||||
lv_obj_set_style_pad_top(_volume_label, 5, 0);
|
||||
|
||||
return _volume_page;
|
||||
@@ -1271,28 +1422,35 @@ static lv_obj_t* create_volume_page(void) {
|
||||
|
||||
static void show_volume_control(void) {
|
||||
ESP_LOGI(TAG, "Showing volume control");
|
||||
|
||||
|
||||
// Load saved volume from system
|
||||
_current_volume = system_getVolume();
|
||||
ESP_LOGI(TAG, "Loaded volume from system: %d", _current_volume);
|
||||
|
||||
lv_obj_t* menu = create_menu_container();
|
||||
if (!menu) {
|
||||
ESP_LOGE(TAG, "Failed to create menu container for volume control");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
lv_obj_t* volume_page = create_volume_page();
|
||||
if (!volume_page) {
|
||||
ESP_LOGE(TAG, "Failed to create volume page");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
lv_menu_set_page(menu, volume_page);
|
||||
_currentPage = volume_page;
|
||||
_mode = GUI_MENU; // Keep in menu mode
|
||||
|
||||
menu_hide_headers(menu);
|
||||
|
||||
|
||||
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
if (_voltageLabel != NULL) {
|
||||
lv_obj_add_flag(_voltageLabel, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Volume control displayed");
|
||||
}
|
||||
|
||||
@@ -1328,6 +1486,17 @@ static bool menu_stack_is_empty(void) {
|
||||
static void menu_go_back(void) {
|
||||
ESP_LOGI(TAG, "Menu go back requested");
|
||||
|
||||
// Save volume if exiting from volume page
|
||||
if (_currentPage == _volume_page) {
|
||||
system_setVolume(_current_volume);
|
||||
esp_err_t ret = system_saveVolume();
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Volume %d saved when exiting volume menu", _current_volume);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to save volume: %s", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
if (menu_stack_is_empty()) {
|
||||
ESP_LOGI(TAG, "Menu stack empty, returning to bubble mode");
|
||||
_mode = GUI_BUBBLE;
|
||||
@@ -1373,6 +1542,7 @@ static void update_volume_display(int volume) {
|
||||
UNLOCK();
|
||||
|
||||
_current_volume = volume;
|
||||
system_setVolume(volume); // Update system state in real-time
|
||||
ESP_LOGI(TAG, "Volume display updated to %d%%", volume);
|
||||
}
|
||||
}
|
||||
@@ -1438,7 +1608,7 @@ static void calibration_click_cb(lv_event_t * e) {
|
||||
// Show "Calibrated" message centered
|
||||
lv_obj_t* label = lv_label_create(container);
|
||||
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_center(label);
|
||||
}
|
||||
@@ -1467,7 +1637,7 @@ static void calibration_click_cb(lv_event_t * e) {
|
||||
// Show "Cleared" message centered
|
||||
lv_obj_t* label = lv_label_create(container);
|
||||
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_center(label);
|
||||
}
|
||||
@@ -1545,6 +1715,9 @@ static void show_calibration_menu(void) {
|
||||
|
||||
lv_obj_remove_flag(menu, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
if (_voltageLabel != NULL) {
|
||||
lv_obj_add_flag(_voltageLabel, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Calibration menu displayed");
|
||||
}
|
||||
@@ -1556,11 +1729,11 @@ static void build_scrollable_menu(void) {
|
||||
lv_style_init(&_styleFocusedBtn);
|
||||
lv_style_init(&_styleUnfocusedBtn);
|
||||
|
||||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // gray bg
|
||||
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_hex(0xFF8800));
|
||||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0x00,0x00,0x00)); // black bg
|
||||
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_text_color(&_styleUnfocusedBtn,lv_color_black());
|
||||
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0xFF,0xFF,0x00)); // bright yellow bg
|
||||
lv_style_set_text_color(&_styleFocusedBtn, lv_color_black());
|
||||
|
||||
|
||||
// 2) Inside it, create the lv_menu and hide its sidebar/header
|
||||
@@ -1653,13 +1826,28 @@ static void gui_task(void *pvParameters)
|
||||
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||||
UNLOCK();
|
||||
|
||||
// Initialize backlight timeout
|
||||
_lastButtonPress = xTaskGetTickCount();
|
||||
_backlightOn = true;
|
||||
|
||||
ESP_LOGI(TAG, "Start GUI Task...");
|
||||
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) {
|
||||
#if 0
|
||||
case (KEY_UP << KEY_LONG_PRESS):
|
||||
@@ -1795,6 +1983,18 @@ static void gui_task(void *pvParameters)
|
||||
{
|
||||
ImuData_t d = system_getImuData();
|
||||
bubble_setValue(_bubble, -d.angle);
|
||||
|
||||
#if 0
|
||||
// Update voltage display
|
||||
lvgl_port_lock(0);
|
||||
int voltage_mv = system_getBatteryVoltage();
|
||||
if (_voltageLabel != NULL) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%d mV", voltage_mv);
|
||||
lv_label_set_text(_voltageLabel, buf);
|
||||
}
|
||||
lvgl_port_unlock();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
28
main/main.c
28
main/main.c
@@ -28,6 +28,7 @@
|
||||
#include "gpio.h"
|
||||
#include "keypad.h"
|
||||
#include "system.h"
|
||||
#include "battery.h"
|
||||
|
||||
|
||||
|
||||
@@ -313,15 +314,21 @@ void app_main(void)
|
||||
bt_app_init();
|
||||
print_heap_info("POST_BLUETOOTH");
|
||||
|
||||
// Initialize battery monitoring
|
||||
ESP_ERROR_CHECK(battery_init());
|
||||
battery_start_monitoring_task();
|
||||
print_heap_info("POST_BATTERY");
|
||||
|
||||
gpio_set_level(PIN_NUM_LED_0, 1);
|
||||
gpio_set_level(PIN_NUM_LED_1, 1);
|
||||
gpio_set_level(PIN_NUM_LED_2, 1);
|
||||
|
||||
gpio_set_level(PIN_NUM_LED_1, 1);
|
||||
gpio_set_level(PIN_NUM_LED_2, 1);
|
||||
|
||||
gui_start();
|
||||
print_heap_info("POST_GUI");
|
||||
gpio_set_level(PIN_NUM_LED_2, 1);
|
||||
|
||||
|
||||
battery_start_monitoring_task();
|
||||
#if 0
|
||||
|
||||
keypad_start();
|
||||
@@ -357,22 +364,7 @@ void app_main(void)
|
||||
#else
|
||||
while (1)
|
||||
{
|
||||
// Read charge status and update system
|
||||
bool is_charging = gpio_get_level(PIN_NUM_CHARGE_STATUS);
|
||||
system_setChargeStatus(is_charging);
|
||||
|
||||
if (is_charging)
|
||||
{
|
||||
gpio_set_level(PIN_NUM_LED_0, 0);
|
||||
gpio_set_level(PIN_NUM_LED_1, 1);
|
||||
gpio_set_level(PIN_NUM_LED_2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpio_set_level(PIN_NUM_LED_0, 1);
|
||||
gpio_set_level(PIN_NUM_LED_1, 0);
|
||||
gpio_set_level(PIN_NUM_LED_2, 0);
|
||||
}
|
||||
|
||||
system_processNvsRequests();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
214
main/system.c
214
main/system.c
@@ -5,6 +5,7 @@
|
||||
#include "esp_timer.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
static SystemState_t _systemState;
|
||||
|
||||
static EventGroupHandle_t _systemEvent;
|
||||
@@ -13,6 +14,9 @@ static EventManager_t _eventManager;
|
||||
static QueueHandle_t _nvsRequestQueue;
|
||||
static const char* NVS_NAMESPACE = "bt_devices";
|
||||
static const char* NVS_KEY_COUNT = "count";
|
||||
static const char* NVS_NAMESPACE_SETTINGS = "settings";
|
||||
static const char* NVS_KEY_VOLUME = "volume";
|
||||
static const char* NVS_KEY_SWAP_LR = "swap_lr";
|
||||
|
||||
static esp_err_t nvs_load_devices_internal(paired_device_t *devices, size_t *count);
|
||||
static esp_err_t nvs_save_devices_internal(const paired_device_t *devices, size_t count);
|
||||
@@ -23,6 +27,10 @@ void system_init(void)
|
||||
_systemState.primaryAxis = PRIMARY_AXIS;
|
||||
_systemState.pairedDeviceCount = 0;
|
||||
_systemState.isCharging = false;
|
||||
_systemState.swapLR = false;
|
||||
_systemState.volume = 50; // Default volume
|
||||
_systemState.batteryVoltage_mv = 0;
|
||||
_systemState.batteryPercentage = 0;
|
||||
|
||||
_systemEvent = xEventGroupCreate();
|
||||
|
||||
@@ -30,6 +38,12 @@ void system_init(void)
|
||||
_eventManager.mutex = xSemaphoreCreateMutex();
|
||||
|
||||
system_initNvsService();
|
||||
|
||||
// Load saved volume from NVS
|
||||
system_loadVolume();
|
||||
|
||||
// Load saved swap L/R setting from NVS
|
||||
system_loadSwapLR();
|
||||
}
|
||||
|
||||
int system_getPrimaryAxis(void)
|
||||
@@ -74,6 +88,66 @@ bool system_getChargeStatus(void)
|
||||
return charging;
|
||||
}
|
||||
|
||||
void system_setBatteryVoltage(int voltage_mv)
|
||||
{
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
_systemState.batteryVoltage_mv = voltage_mv;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
}
|
||||
|
||||
int system_getBatteryVoltage(void)
|
||||
{
|
||||
int voltage;
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
voltage = _systemState.batteryVoltage_mv;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
return voltage;
|
||||
}
|
||||
|
||||
void system_setBatteryPercentage(int percentage)
|
||||
{
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
_systemState.batteryPercentage = percentage;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
}
|
||||
|
||||
int system_getBatteryPercentage(void)
|
||||
{
|
||||
int percentage;
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
percentage = _systemState.batteryPercentage;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
return percentage;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Save to NVS
|
||||
system_saveSwapLR();
|
||||
}
|
||||
|
||||
void system_setZeroAngle(void)
|
||||
{
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
@@ -231,6 +305,146 @@ void system_requestVolumeDown(void) {
|
||||
system_notifyAll(EM_EVENT_VOLUME_DOWN);
|
||||
}
|
||||
|
||||
void system_setVolume(int volume) {
|
||||
if (volume < 0) volume = 0;
|
||||
if (volume > 100) volume = 100;
|
||||
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
_systemState.volume = volume;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
|
||||
ESP_LOGI("system", "Volume set to %d", volume);
|
||||
}
|
||||
|
||||
int system_getVolume(void) {
|
||||
int volume;
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
volume = _systemState.volume;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
return volume;
|
||||
}
|
||||
|
||||
esp_err_t system_saveVolume(void) {
|
||||
nvs_handle_t nvs_handle;
|
||||
esp_err_t ret;
|
||||
|
||||
int volume = system_getVolume();
|
||||
|
||||
ret = nvs_open(NVS_NAMESPACE_SETTINGS, NVS_READWRITE, &nvs_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("system", "Failed to open NVS namespace for volume write: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_i32(nvs_handle, NVS_KEY_VOLUME, volume);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("system", "Failed to write volume to NVS: %s", esp_err_to_name(ret));
|
||||
nvs_close(nvs_handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_commit(nvs_handle);
|
||||
nvs_close(nvs_handle);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI("system", "Volume %d saved to NVS", volume);
|
||||
} else {
|
||||
ESP_LOGE("system", "Failed to commit volume to NVS: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t system_loadVolume(void) {
|
||||
nvs_handle_t nvs_handle;
|
||||
esp_err_t ret;
|
||||
int32_t volume = 50; // Default value
|
||||
|
||||
ret = nvs_open(NVS_NAMESPACE_SETTINGS, NVS_READONLY, &nvs_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGI("system", "No saved volume found, using default: %ld", volume);
|
||||
system_setVolume(volume);
|
||||
return ESP_OK; // Not an error, just means no saved value
|
||||
}
|
||||
|
||||
ret = nvs_get_i32(nvs_handle, NVS_KEY_VOLUME, &volume);
|
||||
nvs_close(nvs_handle);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI("system", "Loaded volume from NVS: %ld", volume);
|
||||
system_setVolume(volume);
|
||||
} else if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
||||
ESP_LOGI("system", "No saved volume found, using default: %ld", volume);
|
||||
system_setVolume(volume);
|
||||
ret = ESP_OK; // Not an error
|
||||
} else {
|
||||
ESP_LOGE("system", "Failed to read volume from NVS: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t system_saveSwapLR(void) {
|
||||
nvs_handle_t nvs_handle;
|
||||
esp_err_t ret;
|
||||
|
||||
bool swapLR = system_getSwapLR();
|
||||
|
||||
ret = nvs_open(NVS_NAMESPACE_SETTINGS, NVS_READWRITE, &nvs_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("system", "Failed to open NVS namespace for swap L/R write: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_set_u8(nvs_handle, NVS_KEY_SWAP_LR, swapLR ? 1 : 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("system", "Failed to write swap L/R to NVS: %s", esp_err_to_name(ret));
|
||||
nvs_close(nvs_handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = nvs_commit(nvs_handle);
|
||||
nvs_close(nvs_handle);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI("system", "Swap L/R %s saved to NVS", swapLR ? "ON" : "OFF");
|
||||
} else {
|
||||
ESP_LOGE("system", "Failed to commit swap L/R to NVS: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t system_loadSwapLR(void) {
|
||||
nvs_handle_t nvs_handle;
|
||||
esp_err_t ret;
|
||||
uint8_t swapLR_u8 = 0; // Default value (OFF)
|
||||
|
||||
ret = nvs_open(NVS_NAMESPACE_SETTINGS, NVS_READONLY, &nvs_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGI("system", "No saved swap L/R found, using default: OFF");
|
||||
system_setSwapLR(false);
|
||||
return ESP_OK; // Not an error, just means no saved value
|
||||
}
|
||||
|
||||
ret = nvs_get_u8(nvs_handle, NVS_KEY_SWAP_LR, &swapLR_u8);
|
||||
nvs_close(nvs_handle);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
bool swapLR = (swapLR_u8 != 0);
|
||||
ESP_LOGI("system", "Loaded swap L/R from NVS: %s", swapLR ? "ON" : "OFF");
|
||||
system_setSwapLR(swapLR);
|
||||
} else if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
||||
ESP_LOGI("system", "No saved swap L/R found, using default: OFF");
|
||||
system_setSwapLR(false);
|
||||
ret = ESP_OK; // Not an error
|
||||
} else {
|
||||
ESP_LOGE("system", "Failed to read swap L/R from NVS: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void system_initNvsService(void) {
|
||||
_nvsRequestQueue = xQueueCreate(10, sizeof(nvs_request_t));
|
||||
if (_nvsRequestQueue == NULL) {
|
||||
|
||||
@@ -55,6 +55,16 @@ typedef struct SystemState_s
|
||||
// Charge status
|
||||
bool isCharging;
|
||||
|
||||
// Swap L/R audio channels
|
||||
bool swapLR;
|
||||
|
||||
// Volume setting (0-100)
|
||||
int volume;
|
||||
|
||||
// Battery monitoring
|
||||
int batteryVoltage_mv;
|
||||
int batteryPercentage;
|
||||
|
||||
} SystemState_t;
|
||||
|
||||
|
||||
@@ -90,6 +100,17 @@ float system_getAngle(void);
|
||||
void system_setChargeStatus(bool charging);
|
||||
bool system_getChargeStatus(void);
|
||||
|
||||
void system_setBatteryVoltage(int voltage_mv);
|
||||
int system_getBatteryVoltage(void);
|
||||
void system_setBatteryPercentage(int percentage);
|
||||
int system_getBatteryPercentage(void);
|
||||
|
||||
void system_setSwapLR(bool swap);
|
||||
bool system_getSwapLR(void);
|
||||
void system_toggleSwapLR(void);
|
||||
esp_err_t system_saveSwapLR(void);
|
||||
esp_err_t system_loadSwapLR(void);
|
||||
|
||||
void system_setZeroAngle(void);
|
||||
void system_clearZeroAngle(void);
|
||||
float system_getZeroAngle(void);
|
||||
@@ -111,6 +132,10 @@ int system_getBtDeviceIndex(void);
|
||||
// Volume control functions
|
||||
void system_requestVolumeUp(void);
|
||||
void system_requestVolumeDown(void);
|
||||
void system_setVolume(int volume);
|
||||
int system_getVolume(void);
|
||||
esp_err_t system_saveVolume(void);
|
||||
esp_err_t system_loadVolume(void);
|
||||
|
||||
// NVS Service
|
||||
typedef enum {
|
||||
|
||||
Reference in New Issue
Block a user