Compare commits

...

2 Commits

Author SHA1 Message Date
Brent Perteet
4697b369db Merge branch 'master' of https://git.sparksoftdesign.com/firstpass/soundshot 2025-07-22 14:09:47 -05:00
Brent Perteet
dc2885e02e Adding environment setup and instructions 2025-07-22 14:04:27 -05:00
21 changed files with 11115 additions and 5358 deletions

40
.vscode/settings.json vendored
View File

@@ -1,3 +1,41 @@
{
"idf.pythonInstallPath": "/usr/bin/python"
"C_Cpp.intelliSenseEngine": "default",
"idf.espIdfPathWin": "C:\\Users\\brent.RPX\\esp\\v5.3.1\\esp-idf",
"idf.openOcdConfigs": [
"board/esp32-wrover-kit-3.3v.cfg"
],
"idf.portWin": "COM4",
"idf.toolsPathWin": "C:\\Users\\brent.RPX\\.espressif\\tools",
"idf.flashType": "UART",
"files.associations": {
"esp_system.h": "c",
"task.h": "c",
"bt_app_core.h": "c",
"math.h": "c",
"esp_log.h": "c",
"freertos.h": "c",
"queue.h": "c",
"unistd.h": "c",
"inttypes.h": "c",
"esp_idf_version.h": "c",
"timers.h": "c",
"lvgl.h": "c",
"esp_timer.h": "c",
"esp_bt_device.h": "c",
"esp_lvgl_port.h": "c",
"lv_global.h": "c",
"stdio.h": "c",
"compare": "c",
"cstdint": "c",
"spi_master.h": "c",
"bitset": "c",
"format": "c",
"system.h": "c",
"esp_lcd_panel_ops.h": "c",
"semphr.h": "c",
"lv_spinbox.h": "c",
"lv_slider.h": "c",
"lv_menu.h": "c"
},
"git.ignoreLimitWarning": true
}

214
README.md
View File

@@ -1,3 +1,216 @@
<<<<<<< HEAD
## 🐳 ESP-IDF Dockerized Development Environment (Windows)
This project includes a fully containerized [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html) development environment using **Docker** and **Visual Studio Code**.
Follow these instructions to set up, build, and flash the firmware inside a VS Code **Dev Container** on **Windows**.
---
### ✅ Prerequisites
Ensure the following are installed on your system:
1. [**Docker Desktop for Windows**](https://www.docker.com/products/docker-desktop)
* Enable **WSL 2 backend** during installation.
2. [**Visual Studio Code**](https://code.visualstudio.com/)
3. [**Dev Containers Extension**](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
* In VS Code: `Extensions → Search "Dev Containers" → Install`
4. [**Espressif IDF Extension** for VS Code](https://marketplace.visualstudio.com/items?itemName=espressif.esp-idf-extension)
* In VS Code: `Extensions → Search "ESP-IDF" → Install`
> 💡 **Optional (recommended):** Install [WSL 2 with Ubuntu](https://learn.microsoft.com/en-us/windows/wsl/install) for improved Linux compatibility inside Docker.
---
### 🚀 Getting Started
#### 1. Clone this repository
```bash
git clone https://git.sparksoftdesign.com/firstpass/soundshot.git
cd soundshot
```
---
You can also clone with TortoiseGit or VS Code directly.
#### 2. Open in Visual Studio Code
From the project root:
```bash
code .
```
> Ensure you're opening the folder that contains `.devcontainer/`.
---
#### 3. Reopen in Dev Container
In VS Code:
* Press `F1` or `Ctrl+Shift+P`
* Run: **Dev Containers: Reopen in Container**
VS Code will:
* Build the Docker image (based on the provided `Dockerfile`)
* Set up the ESP-IDF environment
* Install extensions automatically
---
#### 4. Verify Environment
Once setup is complete:
* A terminal should launch inside the container
* Run:
```bash
idf.py --version
```
to confirm ESP-IDF is active and available.
---
#### 5. Build the Firmware
Inside the containers terminal:
```bash
idf.py build
```
You should see standard ESP-IDF build output and a `.bin` firmware file in the `build/` directory.
---
## 🛠️ ESP32 Programming Setup & Workflow Guide
This project provides a fully portable development and flashing toolchain for ESP32, using Python scripts and configuration-driven workflows.
---
### 📦 1. Environment Setup
> Run once after cloning the repo (Windows only)
```bash
setup-env.bat
```
This:
* Creates a `.venv` virtual environment
* Installs required Python packages (`esptool`, `pyserial`, etc.)
* Prepares the tooling for flashing and monitoring
---
### ⚙️ 2. Configure `settings.json`
Customize `settings.json` in the project root:
```json
{
"project_name": "soundshot",
"chip": "esp32",
"port": "COM3",
"baud": 460800,
"flash_mode": "dio",
"flash_freq": "48m",
"flash_size": "2MB",
"before": "default-reset",
"after": "hard-reset"
}
```
This controls:
* Flashing chip type and parameters
* Serial monitor settings
* Build artifact naming
---
### 🚀 3. Flashing the Firmware
After building your firmware (e.g. via VS Code + Dev Container):
```bash
build/
├── bootloader/bootloader.bin
├── soundshot.bin ← ← ← (project_name)
├── partition_table/partition-table.bin
```
Run the flash tool:
```bash
flash.bat
```
This:
* Loads `settings.json`
* Verifies all required binaries
* Calls `esptool.py` with all correct arguments and offsets
---
### 🛰 4. Monitoring Serial Output
To view device output:
```bash
monitor.bat
```
Features:
* Uses `pyserial`
* Displays real-time output from the ESP32
* Preserves color formatting (if enabled in firmware)
* Cleanly exits with `Ctrl+C` (no "Terminate batch job" prompts)
> Tip: Run `idf.py menuconfig` and enable
> `Component config → Log output → Enable color log output`
---
### ✅ Workflow Summary
| Action | Tool | Command |
| ------------------ | ------------------- | ----------------- |
| Install tooling | `setup-env.bat` | `setup-env.bat` |
| Flash device | `flash.py` script | `flash.bat` |
| Monitor logs | `monitor.py` script | `monitor.bat` |
| Configure behavior | `settings.json` | *(edit manually)* |
---
### 🧰 Notes
* The ESP-IDF version is pinned in the Dockerfile (e.g., `espressif/idf:v5.2.1`)
* The container automatically runs `source $IDF_PATH/export.sh` to prepare the environment.
* VS Code extensions (`.devcontainer.json`) include:
* `ms-vscode.cpptools`
* `ms-vscode.cmake-tools`
* `espressif.esp-idf-extension`
---
### 🛠 Troubleshooting
* If `idf.py` is not found, make sure the container terminal sources `export.sh`
=======
| Supported Targets | ESP32 |
| ----------------- | ----- |
@@ -88,3 +301,4 @@ I (111040) BT_AV: a2dp disconnected
* The raw PCM media stream in the example is generated by a sequence of random number, so the sound played on the sink side will be piercing noise.
* As a usage limitation, ESP32 A2DP source can support at most one connection with remote A2DP sink devices. Also, A2DP source cannot be used together with A2DP sink at the same time, but can be used with other profiles such as SPP and HFP.
* Usually, the `ACL_CONN_NUM` is set to `2` but not `1` as one is used for a2dp connection and the other is used for avrcp connection.
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442

22
flash.bat Normal file
View File

@@ -0,0 +1,22 @@
@echo off
REM =============================================
REM Flash ESP32 using Python script + settings.json
REM =============================================
REM Check for venv
IF NOT EXIST .venv\Scripts\python.exe (
echo [ERROR] Python virtual environment not found at .venv\
echo Run setup-env.bat first.
pause
exit /b 1
)
REM Activate venv and run flash.py
call .venv\Scripts\activate.bat
echo [INFO] Flashing firmware using flash.py and settings.json...
python flash_tool\flash.py
REM Pause to view output
echo.
pause

60
flash_tool/flash.py Normal file
View File

@@ -0,0 +1,60 @@
import json
import subprocess
import sys
from pathlib import Path
def main():
root = Path(__file__).resolve().parent.parent
config_file = root / "settings.json"
if not config_file.exists():
print(f"[ERROR] settings.json not found at {config_file}")
sys.exit(1)
with open(config_file) as f:
cfg = json.load(f)
project = cfg["project_name"]
chip = cfg.get("chip", "esp32")
port = cfg.get("port", "COM3")
baud = cfg.get("flash_baud", 460800)
flash_mode = cfg.get("flash_mode", "dio")
flash_freq = cfg.get("flash_freq", "40m")
flash_size = cfg.get("flash_size", "4MB")
before = cfg.get("before", "default_reset")
after = cfg.get("after", "hard_reset")
# Define binaries and offsets
bootloader = root / "build" / "bootloader" / "bootloader.bin"
firmware = root / "build" / f"{project}.bin"
partition = root / "build" / "partition_table" / "partition-table.bin"
# Verify binaries exist
for f in [bootloader, firmware, partition]:
if not f.exists():
print(f"[ERROR] File not found: {f}")
sys.exit(1)
# Build esptool command
cmd = [
sys.executable, "-m", "esptool",
"--chip", chip,
"--port", port,
"--baud", str(baud),
"--before", before,
"--after", after,
"write-flash", # ✅ hyphenated command
"--flash-mode", flash_mode, # ✅ hyphenated flags
"--flash-freq", flash_freq,
"--flash-size", flash_size,
"0x0", str(bootloader),
"0x10000", str(firmware),
"0x8000", str(partition)
]
print(f"[Flashing] Running: {' '.join(cmd)}")
subprocess.run(cmd, check=True)
if __name__ == "__main__":
main()

45
flash_tool/monitor.py Normal file
View File

@@ -0,0 +1,45 @@
import json
import serial
import sys
import time
from pathlib import Path
# import colorama
# colorama.just_fix_windows_console()
print("\x1b[0;32mThis should be green\x1b[0m")
print("\x1b[0;31mThis should be red\x1b[0m")
def main():
# Load config
root = Path(__file__).resolve().parent.parent
config_file = root / "settings.json"
if not config_file.exists():
print(f"Error: settings.json not found at {config_file}")
sys.exit(1)
with open(config_file) as f:
cfg = json.load(f)
port = cfg.get("port", "COM3")
baud = cfg.get("monitor_baud", 115200)
print(f"[Serial Monitor] Connecting to {port} at {baud} baud...")
try:
with serial.Serial(port, baud, timeout=0.5) as ser:
print("[Serial Monitor] Press Ctrl+C to exit.\n")
while True:
if ser.in_waiting:
line = ser.readline().decode(errors="replace")
if line:
print(line, end="")
time.sleep(0.01)
except KeyboardInterrupt:
print("\n[Serial Monitor] Disconnected.")
except serial.SerialException as e:
print(f"[Error] Could not open serial port {port}: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

14
flashmon.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
REM =============================================
REM ESP32 Serial Monitor using settings.json
REM =============================================
IF NOT EXIST .venv\Scripts\python.exe (
echo [ERROR] .venv not found. Run setup-env.bat first.
pause
exit /b 1
)
call .venv\Scripts\activate.bat
python flash_tool\flash.py
python flash_tool\monitor.py

View File

@@ -1,3 +1,17 @@
<<<<<<< HEAD
idf_component_register(SRCS "system.c.LOCAL.c" "bt_app.c" "system.c" "bubble.c" "keypad.c" "main.c"
"gui.c"
"lsm6dsv.c"
INCLUDE_DIRS "."
REQUIRES "driver"
"esp_lcd"
"lvgl"
"esp_lvgl_port"
"esp_timer"
"nvs_flash"
"bt")
=======
idf_component_register(SRCS "bt_app.c" "system.c" "bubble.c" "keypad.c" "main.c"
"gui.c"
@@ -10,3 +24,5 @@ idf_component_register(SRCS "bt_app.c" "system.c" "bubble.c" "keypad.c" "main.c"
"esp_timer"
"nvs_flash"
"bt")
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
#ifndef GPIO_H
#define GPIO_H
@@ -26,4 +27,34 @@
#define PIN_NUM_BK_LIGHT 5 // Backlight control pin, -1 if not used
#endif
=======
#ifndef GPIO_H
#define GPIO_H
// Define keypad buttons
#define PIN_NUM_BUTTON_0 36
#define PIN_NUM_BUTTON_1 39
#define PIN_NUM_LED_1 32
#define PIN_NUM_LED_2 33
#define PIN_NUM_nON 26
// Define GPIO pins
#ifdef DEVKIT
#define PIN_NUM_MOSI 23 // SDA pin for LCD
#define PIN_NUM_CLK 18 // SCL pin for LCD
#define PIN_NUM_CS 2 // CS pin
#define PIN_NUM_DC 12 // Data/Command pin (RS)
#define PIN_NUM_RST 13 // Reset pin
#define PIN_NUM_BK_LIGHT -1 // Backlight control pin, -1 if not used
#else
#define PIN_NUM_MOSI 23 // SDA pin for LCD
#define PIN_NUM_CLK 18 // SCL pin for LCD
#define PIN_NUM_CS 12 // CS pin
#define PIN_NUM_DC 15 // Data/Command pin (RS)
#define PIN_NUM_RST 13 // Reset pin
#define PIN_NUM_BK_LIGHT 5 // Backlight control pin, -1 if not used
#endif
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442
#endif

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
#include <stdio.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
@@ -616,4 +617,624 @@ static void gui_task(void)
=======
#include <stdio.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "lvgl.h"
#include "esp_lvgl_port.h"
#include "gui.h"
#include "gpio.h"
#include "keypad.h"
#include "bubble.h"
#include "system.h"
#define DEVKIT
#undef DEVKIT
static const char *TAG = "gui";
#define UNLOCK() lvgl_port_unlock()
#define LOCK() lvgl_port_lock(0)
// LCD Pin Configuration
#define LCD_PIXEL_CLOCK_HZ (5 * 1000 * 1000)
#define LCD_SPI_HOST SPI2_HOST
#define PIN_NUM_nON 26
// ST7735S properties
#define LCD_H_RES 160
#define LCD_V_RES 80
#define LCD_CMD_BITS 8
#define LCD_PARAM_BITS 8
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_RGB
#define LCD_BITS_PER_PIXEL 16
static esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
static lv_disp_t *disp = NULL;
static lv_obj_t *imu_label = NULL; // Label for IMU data
static lv_style_t style_mono8;
typedef struct
{
int selected;
int count;
lv_obj_t *obj;
} menu_context_t;
static void gui_task(void);
static void createBubble(lv_obj_t * scr);
static void build_scrollable_menu(void);
static void currentFocusIndex(menu_context_t *ctx);
#define MAX_ITEMS 10
#define VISIBLE_ITEMS 3
#define MENU_MAX_STRING_LENGTH 30
static const char *menu_items[MAX_ITEMS] = {
"V-Moda Crossfade", "Item 2", "Item 3", "Item 4",
"Item 5", "Item 6", "Item 7", "Item 8",
"Item 9", "Item 10"
};
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 * _menu = NULL;
static lv_obj_t *_currentPage = NULL;
static GuiMode_t _mode = GUI_BUBBLE;
/* 1. Prepare a default (unfocused) style */
static lv_style_t _styleUnfocusedBtn;
/* 2. Prepare a focus style */
static lv_style_t _styleFocusedBtn;
static menu_context_t _menuContext;
static bool notify_lvgl_flush_ready(void *user_ctx) {
if (disp) {
lv_display_flush_ready(disp);
}
return true;
}
static void create_lvgl_demo(void)
{
lvgl_port_lock(0);
// Create a screen with black background
lv_obj_t *scr = lv_scr_act();
createBubble(scr);
//build_scrollable_menu();
lvgl_port_unlock();
}
static void lcd_init(void)
{
ESP_LOGI(TAG, "Initialize SPI bus");
spi_bus_config_t buscfg = {
.sclk_io_num = PIN_NUM_CLK,
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_H_RES * LCD_V_RES * 2
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = PIN_NUM_DC,
.cs_gpio_num = PIN_NUM_CS,
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
.lcd_cmd_bits = LCD_CMD_BITS,
.lcd_param_bits = LCD_PARAM_BITS,
.spi_mode = 3,
.trans_queue_depth = 10,
.user_ctx = NULL,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &io_handle));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_NUM_RST,
.rgb_endian = LCD_RGB_ENDIAN_BGR,
.bits_per_pixel = LCD_BITS_PER_PIXEL,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 1, 26)); // ST7735S typically needs these offsets
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
}
static void lvgl_init(void)
{
lv_init();
#if 1
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4, // LVGL task priority
.task_stack = 16384, // LVGL task stack size
.task_affinity = 0, // LVGL task can run on any core
.task_max_sleep_ms = 500, // Maximum sleep in LVGL task
.timer_period_ms = 5 // LVGL timer period
};
ESP_ERROR_CHECK(lvgl_port_init(&lvgl_cfg));
#endif
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = LCD_H_RES * LCD_V_RES * 2,
.double_buffer = false,
.hres = LCD_H_RES,
.vres = LCD_V_RES,
.monochrome = false,
.rotation = {
.swap_xy = true,
.mirror_x = true,
.mirror_y = false,
},
.flags = {
//.buff_dma = true,
.swap_bytes = true,
}
};
disp = lvgl_port_add_disp(&disp_cfg);
//lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
}
static void createBubble(lv_obj_t * scr)
{
// 2) Create a bubble level of size 200×60, with range [30°, +30°], initial 0°:
lv_obj_t * level = bubble_create(scr, 150, 40, -10.0f, +10.0f, 0.0f);
lv_obj_align(level, LV_ALIGN_CENTER, 0, 0);
// 3) … Later, when you read your accelerometer or keypadderived angle …
float new_angle = 10.0f;
bubble_setValue(level, new_angle);
// 4) You can call bubble_level_set_value(level, …) as often as you like.
// Each call invalidates the object and LVGL will call the draw callback
// (usually on the next tick, or immediately if LVGL is idle).
_bubble = level;
}
void gui_start(void)
{
// Initialize LCD
lcd_init();
// Initialize LVGL
lvgl_init();
// Create UI
create_lvgl_demo();
keypad_start();
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
xTaskCreate(gui_task, "gui_task", 4096, NULL, 5, NULL);
}
static uint32_t waitForKeyPress(void)
{
QueueHandle_t q = keypad_getQueue();
uint32_t ev = 0;
if (xQueueReceive(q, &ev, portMAX_DELAY) == pdTRUE)
{
return ev;
}
return 0;
}
static void handleMainMenu(void)
{
lvgl_port_lock(0);
// Create a screen with black background
lv_obj_t *scr = lv_scr_act();
//create_menu(scr);
lvgl_port_unlock();
}
// ───── MENU CONFIG ─────
#define ROW_H 20 // height of each row
static const char * items[] = { // your menu entries
"VMode Crossmade",
"Second Choice",
"Another Option",
"Yet Another",
"Yet Another",
"Last One"
};
#define ITEM_COUNT (sizeof(items)/sizeof(items[0]))
// ───── STATE & HELPERS ─────
static lv_obj_t * btn_array[ITEM_COUNT];
static int selected_idx = 0;
// Called whenever a button is “activated” (Enter/Click)
static void btn_click_cb(lv_event_t * e) {
lv_obj_t * btn = lv_event_get_target(e);
const char * txt = lv_label_get_text(lv_obj_get_child(btn, 0));
ESP_LOGI(TAG, "Activated: %s\n", txt);
}
// Repaint all rows so only btn_array[selected_idx] is highlighted
static void refresh_highlight(void) {
return;
lvgl_port_lock(0);
int index = 0;
lv_obj_t *page = _currentPage;
lv_obj_t * child = NULL;
lv_obj_t * next = lv_obj_get_child(page, index);
lv_obj_t * selected = NULL;
while(next) {
child = next;
ESP_LOGI(TAG, "Child: %p", child);
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_text_color(lv_obj_get_child(child,0),
lv_color_white(), 0);
selected = child;
ESP_LOGI(TAG, "Selected");
}
else
{
lv_obj_set_style_bg_color(child, lv_color_white(), 0);
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
lv_color_black(), 0);
}
index++;
next = lv_obj_get_child(page, index);
}
if (selected)
{
lv_obj_scroll_to_view(selected, LV_ANIM_ON);
}
lvgl_port_unlock();
}
static void menuInc(int inc)
{
LOCK();
currentFocusIndex(&_menuContext);
ESP_LOGI(TAG, "Current Index: %d", _menuContext.selected);
lv_obj_t *next = NULL;
// check if we are at the first or last in the page
lv_obj_t *test = lv_obj_get_child(_currentPage, (inc > 0 ? -1 : 0));
if (_menuContext.obj != test)
{
next = lv_obj_get_child(_currentPage, _menuContext.selected + inc);
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);
_menuContext.obj = next;
_menuContext.selected += inc;
}
UNLOCK();
}
static void menuNext(void)
{
menuInc(1);
}
static void menuPrevious(void)
{
menuInc(-1);
}
// Fire the “clicked” event on the selected row
static void activate_selected(void)
{
LOCK();
lv_obj_send_event(_menuContext.obj, LV_EVENT_CLICKED, NULL);
UNLOCK();
}
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
{
lv_obj_t * btn = lv_btn_create(page);
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);
// style it just like your old list
// lv_obj_set_style_bg_color(btn, lv_color_white(), 0);
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 & center
lv_obj_t * lbl = lv_label_create(btn);
lv_label_set_text(lbl, text);
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);
// click callback
lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
return btn;
}
static int getSelectedIndex(lv_obj_t *page)
{
return -1;
}
static void currentFocusIndex(menu_context_t *ctx)
{
ctx->count = lv_obj_get_child_cnt(_currentPage);
ctx->obj = NULL;
ctx->selected = -1;
// return the index of the currently focused object
for(int i = 0; i < ctx->count; i++) {
lv_obj_t * child = lv_obj_get_child(_currentPage, i);
if (lv_obj_has_state(child, LV_STATE_FOCUSED))
{
ctx->obj = child;
ctx->selected = i;
return;
}
}
}
// ───── BUILD THE MENU ─────
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(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg
lv_style_set_text_color(&_styleUnfocusedBtn,lv_color_black());
// 2) Inside it, create the lv_menu and hide its sidebar/header
lv_obj_t *menu = lv_menu_create(lv_scr_act());
_menu = menu;
lv_obj_set_style_radius(menu, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(menu, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_size(menu, lv_pct(100), lv_pct(100));
lv_obj_center(menu);
lv_obj_set_scrollbar_mode(menu, LV_SCROLLBAR_MODE_AUTO);
lv_obj_t * main = lv_menu_page_create(menu, NULL);
lv_obj_set_style_radius(main, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(main, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_scrollbar_mode(main, LV_SCROLLBAR_MODE_AUTO);
lv_obj_set_size(main, lv_pct(100), lv_pct(100));
lv_menu_set_page(menu, main);
lv_obj_t * tmpObj;
lv_obj_t * calMenu = lv_menu_page_create(menu, NULL);
lv_obj_set_style_radius(calMenu, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_style_border_width(calMenu, 0, LV_PART_MAIN | LV_STATE_ANY);
lv_obj_set_scrollbar_mode(calMenu, LV_SCROLLBAR_MODE_AUTO);
tmpObj = addMenuItem(main, "Bluetooth");
lv_obj_add_state(tmpObj, LV_STATE_FOCUSED);
//lv_obj_add_flag(tmpObj, LV_OBJ_FLAG_USER_1);
tmpObj = addMenuItem(main, "Calibration");
lv_menu_set_load_page_event(menu, tmpObj, calMenu);
tmpObj = addMenuItem(main, "Volume");
addMenuItem(main, "About");
addMenuItem(main, "Exit");
addMenuItem(calMenu, "Calibrate Level");
addMenuItem(calMenu, "Reset Calibration");
addMenuItem(calMenu, "Exit");
_currentPage = main;
// 6) Initial highlight
selected_idx = 0;
refresh_highlight();
}
static void gui_task(void)
{
system_subscribe(xTaskGetCurrentTaskHandle());
// Grab queue handle
QueueHandle_t q = keypad_getQueue();
uint32_t ev = 0;
LOCK();
// _mode = GUI_MENU;
// lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
//lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
UNLOCK();
while (1)
{
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
{
switch (ev) {
case (KEY_UP << KEY_LONG_PRESS):
{
system_setZeroAngle();
break;
}
case (KEY_DOWN << KEY_LONG_PRESS):
{
system_clearZeroAngle();
break;
}
#if 0
case (KEY0 << KEY_SHORT_PRESS):
if (_mode == GUI_MENU)
{
menuNext();
}
ESP_LOGI(TAG, "MAIN: Button 1 SHORT");
break;
case (KEY0 << KEY_LONG_PRESS):
{
if (_mode != GUI_MENU)
{
_mode = GUI_MENU;
lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
}
else
if (_mode == GUI_MENU)
{
activate_selected();
_mode = GUI_BUBBLE;
// lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
// lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
}
break;
}
case (KEY1 << KEY_SHORT_PRESS):
{
if (_mode == GUI_MENU)
{
menuPrevious();
}
break;
}
case (KEY1 << KEY_LONG_PRESS):
ESP_LOGI(TAG, "MAIN: Button 2 LONG");
gpio_set_level(PIN_NUM_nON, 0);
break;
#endif
default:
break;
}
}
if (_mode == GUI_BUBBLE)
{
uint32_t notifiedBits = 0;
// clear on entry (first param), wait for any bit, block forever
xTaskNotifyWait(
0xFFFFFFFF, // clear any old bits on entry
0xFFFFFFFF, // clear bits on exit
&notifiedBits,
pdMS_TO_TICKS(100));
if (notifiedBits & EM_EVENT_NEW_DATA)
{
ImuData_t d = system_getImuData();
bubble_setValue(_bubble, -d.angle);
}
}
}
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442
}

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
#ifndef _KEYPAD_H
#define _KEYPAD_H
@@ -20,4 +21,28 @@ typedef enum
void keypad_start(void);
QueueHandle_t keypad_getQueue(void);
=======
#ifndef _KEYPAD_H
#define _KEYPAD_H
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
typedef enum
{
KEY0 = (1 << 0),
KEY1 = (1 << 1),
} keycode_t;
#define KEY_SHORT_PRESS 0
#define KEY_LONG_PRESS 4
#define KEY_UP KEY0
#define KEY_DOWN KEY1
void keypad_start(void);
QueueHandle_t keypad_getQueue(void);
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442
#endif

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
@@ -289,3 +290,296 @@ void app_main(void)
}
#endif
}
=======
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include "math.h"
#include "esp_system.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "driver/gpio.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "bt_app.h"
#include "lsm6dsv.h"
#include "gui.h"
#include "gpio.h"
#include "keypad.h"
#include "system.h"
/*********************************
* STATIC FUNCTION DECLARATIONS
********************************/
typedef struct {
float alpha; // smoothing factor, 0<alpha<1
float prev_output; // y[n-1]
} LowPassFilter;
// Initialize the filter. alpha = smoothing factor.
// e.g. alpha = dt/(RC+dt) if you derive from cutoff freq & sample time.
// init_output can be 0 or your first sample to avoid startup glitch.
static inline void LPF_Init(LowPassFilter *f, float alpha, float init_output) {
f->alpha = alpha;
f->prev_output = init_output;
}
// Run one input sample through the filter.
// Returns y[n] = alpha * x[n] + (1-alpha) * y[n-1].
static inline float LPF_Update(LowPassFilter *f, float input) {
float out = f->alpha * input + (1.0f - f->alpha) * f->prev_output;
f->prev_output = out;
return out;
}
/*********************************
* STATIC VARIABLE DEFINITIONS
********************************/
static const char *TAG = "main";
/*********************************
* STATIC FUNCTION DEFINITIONS
********************************/
static void init_gpio(void)
{
gpio_config_t io_conf;
// Configure output GPIO
io_conf.pin_bit_mask = (1ULL << PIN_NUM_LED_1) | (1ULL << PIN_NUM_LED_2);
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf);
#if 1
// Configure output power signal
gpio_set_level(PIN_NUM_nON, 1);
io_conf.pin_bit_mask = (1ULL << PIN_NUM_nON);
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf);
#endif
// Configure LCD Backlight GPIO
io_conf.pin_bit_mask = (1ULL << PIN_NUM_BK_LIGHT);
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf);
}
// IMU task to read sensor data
static void imu_task(void *pvParameters) {
lsm6dsv_data_t imu_data;
char data_str[128];
float roll, pitch, yaw;
lsm6dsv_fifo_t fifo;
ImuData_t imu;
LowPassFilter lpf;
LPF_Init(&lpf, FILTER_COEFF, 0);
while (1) {
// uint8_t samples = lsm6dsv_fifo_ready();
//ESP_LOGI(TAG, "Samples: %d", samples);
// while (samples)
// {
// lsm6dsv_fifo_read(&fifo);
// // snprintf(data_str, sizeof(data_str),
// // "tag: %d, count: %d, (%d, %d, %d)",
// // fifo.tag, fifo.count, x, y, z);
// // ESP_LOGI(TAG, "%s", data_str);
// samples--;
// }
// lsm6dsv_getAttitude(&fifo, &roll, &pitch, &yaw);
// snprintf(data_str, sizeof(data_str),
// "r: %0.3f, p: %0.3f, y: %0.3f",
// roll, pitch, yaw);
// ESP_LOGI(TAG, "%s", data_str);
float xz;
float yz;
float xy;
#if 1
if (lsm6dsv_read_data(&imu_data) == ESP_OK)
{
// snprintf(data_str, sizeof(data_str),
// "Acc: %.2f, %.2f, %.2f; "
// "Gyro: %.2f, %.2f, %.2f (%d)\n",
// imu_data.acc_x, imu_data.acc_y, imu_data.acc_z,
// imu_data.gyro_x, imu_data.gyro_y, imu_data.gyro_z, lsm6dsv_fifo_ready());
#if 1
imu.raw[ANGLE_XZ] = atan2f((float)imu_data.acc_z, (float)imu_data.acc_x) * 180 / M_PI;
imu.raw[ANGLE_YZ] = atan2f((float)imu_data.acc_z, (float)imu_data.acc_y) * 180 / M_PI;
imu.raw[ANGLE_XY] = atan2f((float)imu_data.acc_x, (float)imu_data.acc_y) * 180 / M_PI;
float angle;
angle = system_getAngle();
if (angle > MAX_INDICATION_ANGLE)
{
angle = MAX_INDICATION_ANGLE;
}
else
if (angle < -MAX_INDICATION_ANGLE)
{
angle = -MAX_INDICATION_ANGLE;
}
// low pass filter the angle measurement
imu.angle = LPF_Update(&lpf, angle);
//imu.filtered[ANGLE_XY] = LPF_Update(&lpf, imu.raw[ANGLE_XY]);
system_setImuData(imu);
// snprintf(data_str, sizeof(data_str),
// "(%.2f, %.2f, %.2f)", xy, yz, xy);
#endif
#if 0
snprintf(data_str, sizeof(data_str),
"(%.1f, %.1f, %.1f) (%.2f, %.2f, %.2f)",
imu.raw[ANGLE_XZ], imu.raw[ANGLE_YZ], imu.raw[ANGLE_XY],
(float)imu_data.acc_x, (float)imu_data.acc_y, (float)imu_data.acc_z);
ESP_LOGI(TAG, "%s", data_str);
#endif
// Update label text in LVGL task
//lv_label_set_text(imu_label, data_str);
}
#endif
vTaskDelay(pdMS_TO_TICKS(100)); // Update every 100ms
}
}
/*********************************
* MAIN ENTRY POINT
********************************/
void app_main(void)
{
/* initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
init_gpio();
system_init();
// Initialize IMU
ESP_ERROR_CHECK(lsm6dsv_init(22, 21)); // SCL = IO14, SDA = IO15
// Create IMU task
TaskHandle_t h = xTaskCreate(imu_task, "imu_task", 4096, NULL, 5, NULL);
bt_app_init();
gui_start();
gpio_set_level(PIN_NUM_LED_2, 1);
#if 0
keypad_start();
QueueHandle_t q = keypad_getQueue();
uint32_t ev = 0;
//gpio_set_level(PIN_NUM_LED_1, 1);
gpio_set_level(PIN_NUM_LED_2, 1);
// Main loop - LVGL task will run automatically
while (1) {
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
{
switch (ev)
{
case (KEY_UP << KEY_LONG_PRESS):
{
system_setZeroAngle();
break;
}
case (KEY_DOWN << KEY_LONG_PRESS):
{
system_clearZeroAngle();
break;
}
}
}
}
#else
while (1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
#endif
}
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
#include "system.h"
#include "esp_log.h"
@@ -166,4 +167,174 @@ void system_notifyAll(uint32_t eventBits) {
}
xSemaphoreGive(em->mutex);
}
=======
#include "system.h"
#include "esp_log.h"
static SystemState_t _systemState;
static EventGroupHandle_t _systemEvent;
static EventManager_t _eventManager;
void system_init(void)
{
_systemState.zeroAngle = 0.0f;
_systemState.primaryAxis = PRIMARY_AXIS;
EventGroupHandle_t evt = xEventGroupCreate();
_eventManager.count = 0;
_eventManager.mutex = xSemaphoreCreateMutex();
}
int system_getPrimaryAxis(void)
{
int axis;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
axis = _systemState.primaryAxis;
xSemaphoreGive(_eventManager.mutex);
return axis;
}
float system_getAngle(void)
{
float angle;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
angle = _systemState.imu.raw[_systemState.primaryAxis] - _systemState.zeroAngle;
xSemaphoreGive(_eventManager.mutex);
return angle;
}
void system_setZeroAngle(void)
{
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.zeroAngle = _systemState.imu.raw[_systemState.primaryAxis];
ESP_LOGI("system", "Zero: %.1f", _systemState.zeroAngle);
xSemaphoreGive(_eventManager.mutex);
}
void system_clearZeroAngle(void)
{
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.zeroAngle = 0.0f;
ESP_LOGI("system", "Zero: %.1f", _systemState.zeroAngle);
xSemaphoreGive(_eventManager.mutex);
}
float system_getZeroAngle(void)
{
float angle;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
angle = _systemState.zeroAngle;
xSemaphoreGive(_eventManager.mutex);
return angle;
}
void system_setImuData(ImuData_t imu)
{
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
_systemState.imu = imu;
xSemaphoreGive(_eventManager.mutex);
//ESP_LOGI("g", "New IMU Data: %.2f", xy);
system_notifyAll(EM_EVENT_NEW_DATA);
}
ImuData_t system_getImuData(void)
{
ImuData_t imu;
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
imu = _systemState.imu;
xSemaphoreGive(_eventManager.mutex);
return imu;
}
void system_waitForEvent(void)
{
}
SystemState_t *system_getState(void)
{
return &_systemState;
}
BaseType_t system_subscribe(TaskHandle_t task) {
EventManager_t *em = &_eventManager;
ESP_LOGI("g", "Subscribe: %p", task);
if (xSemaphoreTake(em->mutex, portMAX_DELAY) == pdFALSE) {
return pdFAIL;
}
if (em->count < EM_MAX_SUBSCRIBERS) {
// avoid duplicates?
ESP_LOGI("g", "PASS");
em->subscribers[em->count++] = task;
xSemaphoreGive(em->mutex);
return pdPASS;
}
xSemaphoreGive(em->mutex);
return pdFAIL; // full
}
BaseType_t system_unsubscribe(TaskHandle_t task) {
EventManager_t *em = &_eventManager;
BaseType_t removed = pdFAIL;
if (xSemaphoreTake(em->mutex, portMAX_DELAY) == pdTRUE) {
for (size_t i = 0; i < em->count; ++i) {
if (em->subscribers[i] == task) {
// shift down
for (size_t j = i; j + 1 < em->count; ++j)
em->subscribers[j] = em->subscribers[j+1];
em->count--;
removed = pdPASS;
break;
}
}
xSemaphoreGive(em->mutex);
}
return removed;
}
void system_notifyAll(uint32_t eventBits) {
EventManager_t *em = &_eventManager;
if (xSemaphoreTake(em->mutex, portMAX_DELAY) == pdTRUE) {
for (size_t i = 0; i < em->count; ++i) {
// set the bits in each task's notification value
//ESP_LOGI("g", "Notify: %p", em->subscribers[i]);
xTaskNotify(em->subscribers[i],
eventBits,
eSetBits);
}
xSemaphoreGive(em->mutex);
}
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442
}

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
#ifndef SYSTEM_H
#define SYSTEM_H
@@ -72,4 +73,80 @@ BaseType_t system_unsubscribe(TaskHandle_t task);
void system_notifyAll(uint32_t eventBits);
=======
#ifndef SYSTEM_H
#define SYSTEM_H
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#define DISABLE_GUI
enum
{
ANGLE_XY = 0,
ANGLE_XZ,
ANGLE_YZ,
};
typedef struct
{
float raw[3];
float filtered[3];
float angle;
} ImuData_t;
typedef struct SystemState_s
{
ImuData_t imu;
EventGroupHandle_t event;
float zeroAngle;
int primaryAxis;
} SystemState_t;
#define MAX_INDICATION_ANGLE 5.0f
#define FILTER_COEFF 0.2f // 0 to 1 Smaller number is heavier filter
#define PRIMARY_AXIS ANGLE_YZ
#define EM_MAX_SUBSCRIBERS 8 // tweak as needed
#define EM_EVENT_NEW_DATA (1UL<<0)
#define EM_EVENT_ERROR (1UL<<1)
// …add more event bit-masks here…
typedef struct {
TaskHandle_t subscribers[EM_MAX_SUBSCRIBERS];
size_t count;
SemaphoreHandle_t mutex;
} EventManager_t;
void system_init(void);
void system_setImuData(ImuData_t imu);
ImuData_t system_getImuData(void);
int system_getPrimaryAxis(void);
float system_getAngle(void);
void system_setZeroAngle(void);
void system_clearZeroAngle(void);
float system_getZeroAngle(void);
// Subscribe (register) current task to receive events
BaseType_t system_subscribe(TaskHandle_t task);
// Unsubscribe if you ever need
BaseType_t system_unsubscribe(TaskHandle_t task);
// Notify all subscribers of one or more event bits
void system_notifyAll(uint32_t eventBits);
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442
#endif

13
monitor.bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
REM =============================================
REM ESP32 Serial Monitor using settings.json
REM =============================================
IF NOT EXIST .venv\Scripts\python.exe (
echo [ERROR] .venv not found. Run setup-env.bat first.
pause
exit /b 1
)
call .venv\Scripts\activate.bat
python flash_tool\monitor.py

10
prepare_config.py Normal file
View File

@@ -0,0 +1,10 @@
import json
with open("settings.json") as f:
cfg = json.load(f)
with open("project_settings.cmake", "w") as out:
out.write(f'set(PROJECT_NAME "{cfg["project_name"]}")\n')
out.write(f'set(CHIP "{cfg["chip"]}")\n')
out.write(f'set(PORT "{cfg["port"]}")\n')
out.write(f'set(BAUD "{cfg["baud"]}")\n')

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
esptool
pyserial

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
# Override some defaults so BT stack is enabled and
# Classic BT is enabled
CONFIG_BT_ENABLED=y
@@ -7,3 +8,14 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y
=======
# Override some defaults so BT stack is enabled and
# Classic BT is enabled
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442

File diff suppressed because it is too large Load Diff

12
settings.json Normal file
View File

@@ -0,0 +1,12 @@
{
"project_name": "soundshot",
"chip": "esp32h2",
"port": "COM8",
"monitor_baud": 115200,
"flash_baud": 460800,
"flash_mode": "dio",
"flash_freq": "48m",
"flash_size": "2MB",
"before": "default-reset",
"after": "hard-reset"
}

17
setup-env.bat Normal file
View File

@@ -0,0 +1,17 @@
@echo off
setlocal
echo [ESP32 Setup] Creating Python virtual environment...
python -m venv .venv
echo [ESP32 Setup] Activating environment...
call .venv\Scripts\activate.bat
echo [ESP32 Setup] Installing dependencies...
pip install -r requirements.txt
echo [ESP32 Setup] Done!
echo To activate environment later: call .venv\Scripts\activate.bat
endlocal
pause