Compare commits
14 Commits
04d2c71d01
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d427859804 | |||
| 40bea065a7 | |||
|
|
3bce9e772c | ||
|
|
31e0e3a148 | ||
|
|
a272a15bcf | ||
|
|
8a1966ea90 | ||
|
|
b8a3a09e9f | ||
|
|
115105c032 | ||
|
|
19e8ca30c3 | ||
|
|
de7041c1f5 | ||
|
|
fe69dc6f19 | ||
|
|
bca2f6ea9c | ||
|
|
5a893a034c | ||
| 2513a9e7fb |
@@ -6,7 +6,26 @@
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(pip install:*)",
|
||||
"Bash(build_from_spec.bat)"
|
||||
"Bash(build_from_spec.bat)",
|
||||
"mcp__ide__getDiagnostics",
|
||||
"Bash(source:*)",
|
||||
"Bash(chmod:*)",
|
||||
"Bash(python3:*)",
|
||||
"Bash(pyinstaller:*)",
|
||||
"Bash(open:*)",
|
||||
"Bash(dos2unix:*)",
|
||||
"Bash(./build_macos.sh:*)",
|
||||
"Bash(brew install:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(./dist/ESP32_Flasher:*)",
|
||||
"Bash(pkill:*)",
|
||||
"Bash(curl -s http://127.0.0.1:5000/)",
|
||||
"Bash(./build_web_macos.sh:*)",
|
||||
"Bash(./dist/ESP32_Flasher.app/Contents/MacOS/ESP32_Flasher:*)",
|
||||
"Bash(where:*)",
|
||||
"Bash(idf.py size-components:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(git log:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,6 +1,22 @@
|
||||
.pytest_cache/
|
||||
__pycache__/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Python virtual environments
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.venv/
|
||||
|
||||
# PyInstaller build artifacts
|
||||
dist/
|
||||
build/
|
||||
*.spec
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
# esp-idf built binaries
|
||||
build/
|
||||
build_*_*/
|
||||
@@ -21,3 +37,4 @@ dependencies.lock
|
||||
|
||||
managed_components
|
||||
components
|
||||
output
|
||||
37
.vscode/c_cpp_properties.json
vendored
37
.vscode/c_cpp_properties.json
vendored
@@ -1,15 +1,38 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"name": "Win32",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**"
|
||||
"${workspaceFolder}/**",
|
||||
"${env:USERPROFILE}/esp/v5.4.1/esp-idf/components/**",
|
||||
"${env:USERPROFILE}/.espressif/tools/xtensa-esp-elf/**",
|
||||
"${workspaceFolder}/build/config",
|
||||
"${workspaceFolder}/managed_components/**"
|
||||
],
|
||||
"defines": [],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "gnu++17",
|
||||
"intelliSenseMode": "linux-gcc-x64"
|
||||
"defines": [
|
||||
"ESP_PLATFORM"
|
||||
],
|
||||
"compilerPath": "${env:USERPROFILE}/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20241113/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc.exe",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++20",
|
||||
"intelliSenseMode": "gcc-x64"
|
||||
},
|
||||
{
|
||||
"name": "Mac",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"/Users/brent/esp/v5.5.1/esp-idf/components/**",
|
||||
"/Users/brent/.espressif/tools/xtensa-esp-elf/**",
|
||||
"${workspaceFolder}/build/config",
|
||||
"${workspaceFolder}/managed_components/**"
|
||||
],
|
||||
"defines": [
|
||||
"ESP_PLATFORM"
|
||||
],
|
||||
"compilerPath": "/Users/brent/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++20",
|
||||
"intelliSenseMode": "gcc-x64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
||||
50
.vscode/launch.json
vendored
Normal file
50
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Build ESP32 Firmware (Windows)",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "idf.py",
|
||||
"args": ["build"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"windows": {
|
||||
"command": "idf.py"
|
||||
},
|
||||
"problemMatcher": ["$gcc"]
|
||||
},
|
||||
{
|
||||
"name": "Build ESP32 Firmware (macOS)",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "bash",
|
||||
"args": ["-lc", "source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"osx": {
|
||||
"command": "bash"
|
||||
},
|
||||
"problemMatcher": ["$gcc"]
|
||||
},
|
||||
{
|
||||
"name": "Build Windows Flasher",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "${workspaceFolder}/flash_tool/build_from_spec.bat",
|
||||
"cwd": "${workspaceFolder}/flash_tool",
|
||||
"windows": {
|
||||
"command": "${workspaceFolder}/flash_tool/build_from_spec.bat"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Build macOS Flasher",
|
||||
"type": "process",
|
||||
"request": "launch",
|
||||
"command": "${workspaceFolder}/flash_tool/build_macos.sh",
|
||||
"cwd": "${workspaceFolder}/flash_tool",
|
||||
"osx": {
|
||||
"command": "bash",
|
||||
"args": ["${workspaceFolder}/flash_tool/build_macos.sh"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"C_Cpp.intelliSenseEngine": "default",
|
||||
"idf.espIdfPathWin": "C:\\esp\\frameworks\\esp-idf-v5.3.3",
|
||||
"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": "COM4",
|
||||
"idf.toolsPathWin": "C:\\esp\\frameworks\\esp-idf-v5.3.3\\tools\\tools",
|
||||
"idf.portWin": "COM3",
|
||||
"idf.toolsPathWin": "C:\\Users\\Brent.Perteet\\.espressif",
|
||||
"idf.flashType": "UART",
|
||||
"files.associations": {
|
||||
"esp_system.h": "c",
|
||||
@@ -37,8 +38,11 @@
|
||||
"lv_slider.h": "c",
|
||||
"lv_menu.h": "c",
|
||||
"stdint.h": "c",
|
||||
"random": "c"
|
||||
"random": "c",
|
||||
"string.h": "c"
|
||||
},
|
||||
"git.ignoreLimitWarning": true,
|
||||
"idf.pythonInstallPath": "/usr/bin/python"
|
||||
"idf.pythonInstallPath": "C:\\Users\\Brent.Perteet\\.espressif\\tools\\idf-python\\3.11.2\\python.exe",
|
||||
"idf.espIdfPath": "/Users/brent/esp/v5.5.1/esp-idf",
|
||||
"idf.toolsPath": "/Users/brent/.espressif"
|
||||
}
|
||||
|
||||
26
.vscode/tasks.json
vendored
26
.vscode/tasks.json
vendored
@@ -1,11 +1,35 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "adapter",
|
||||
"type": "shell",
|
||||
"command": "idf.py",
|
||||
"args": ["build"],
|
||||
"windows": {
|
||||
"command": "cmd.exe",
|
||||
"args": ["/c", "${env:USERPROFILE}\\esp\\v5.4.1\\esp-idf\\export.bat", "&&", "idf.py", "build"]
|
||||
},
|
||||
"osx": {
|
||||
"command": "bash",
|
||||
"args": ["-lc", "source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build"]
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash",
|
||||
"args": ["-lc", "source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build"]
|
||||
},
|
||||
"group": { "kind": "build", "isDefault": true },
|
||||
"problemMatcher": ["${config:idf.cmakeCompilerArgs}"],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "ESP-IDF: Build (RAM) + Sync to Host",
|
||||
"type": "shell",
|
||||
"command": "bash -lc 'source ${IDF_PATH:-/opt/esp/idf}/export.sh && idf.py build && rsync -a --delete \"$PWD/build/\" /host_build/'",
|
||||
"group": { "kind": "build", "isDefault": true },
|
||||
"group": "build",
|
||||
"problemMatcher": "$gcc"
|
||||
}
|
||||
]
|
||||
|
||||
9
flash_tool/.claude/settings.local.json
Normal file
9
flash_tool/.claude/settings.local.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
110
flash_tool/BUILD_NOTES.md
Normal file
110
flash_tool/BUILD_NOTES.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Build Notes
|
||||
|
||||
## Build Status
|
||||
|
||||
✅ **macOS Build: SUCCESS**
|
||||
- Built on: macOS 15.6 (arm64)
|
||||
- Python Version: 3.9.6 (Xcode system Python with tkinter support)
|
||||
- Output: `dist/ESP32_Flasher.app`
|
||||
- Size: ~7.0 MB
|
||||
- Tested: ✅ Application launches and runs successfully
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### macOS
|
||||
**Important:** Homebrew's Python 3.13 does NOT include tkinter support by default.
|
||||
|
||||
**Solution:** Install python-tk package:
|
||||
```bash
|
||||
brew install python-tk@3.13
|
||||
```
|
||||
|
||||
However, the build system will automatically use the system Python (3.9.6 from Xcode) which has tkinter built-in. This is the recommended approach.
|
||||
|
||||
## Known Issues
|
||||
|
||||
### ~~macOS tkinter Warning~~ - RESOLVED
|
||||
**Previous Issue:** Homebrew Python 3.13 doesn't include tkinter
|
||||
**Solution:** Build system now uses Xcode's Python 3.9.6 which has native tkinter support
|
||||
**Status:** ✅ Fixed - no warnings, app works perfectly
|
||||
|
||||
### Virtual Environment Required (macOS)
|
||||
Due to PEP 668 (externally-managed environments), macOS requires using a virtual environment for pip installations. The build scripts automatically handle this.
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Quick Build (Any Platform)
|
||||
```bash
|
||||
python3 build.py
|
||||
```
|
||||
|
||||
### macOS Specific
|
||||
```bash
|
||||
./build_macos.sh
|
||||
```
|
||||
or
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
pyinstaller ESP32_Flasher_macOS.spec
|
||||
```
|
||||
|
||||
### Windows Specific
|
||||
```batch
|
||||
build_from_spec.bat
|
||||
```
|
||||
|
||||
## Distribution
|
||||
|
||||
### macOS
|
||||
- Distribute the entire `ESP32_Flasher.app` folder (it's a bundle)
|
||||
- Users may need to run: `xattr -cr ESP32_Flasher.app` if macOS blocks it
|
||||
- Consider creating a DMG for easier distribution
|
||||
|
||||
### Windows
|
||||
- Distribute the single `ESP32_Flasher.exe` file
|
||||
- No installation required
|
||||
- May trigger Windows SmartScreen (normal for unsigned apps)
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
flash_tool/
|
||||
├── dist/ # Build output
|
||||
│ ├── ESP32_Flasher.app # macOS bundle
|
||||
│ └── ESP32_Flasher # Standalone binary
|
||||
├── build/ # Build artifacts
|
||||
├── venv/ # Python virtual environment (macOS)
|
||||
├── gui_flasher.py # Source code
|
||||
├── ESP32_Flasher_macOS.spec # macOS build config
|
||||
├── ESP32_Flasher_Windows.spec # Windows build config
|
||||
├── build.py # Universal build script
|
||||
├── build_macos.sh # macOS build script
|
||||
└── build_from_spec.bat # Windows build script
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Testing**: Test the app with actual ESP32 hardware and firmware
|
||||
2. **Icons**: Add custom icons (.icns for macOS, .ico for Windows)
|
||||
3. **Code Signing**: Sign the executables for production distribution
|
||||
4. **DMG Creation**: Package macOS app in a DMG for easier distribution
|
||||
5. **Windows Installer**: Consider creating an NSIS or WiX installer
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "App is damaged" on macOS
|
||||
```bash
|
||||
xattr -cr dist/ESP32_Flasher.app
|
||||
```
|
||||
|
||||
### Port permission issues on Linux
|
||||
```bash
|
||||
sudo usermod -a -G dialout $USER
|
||||
```
|
||||
(Logout/login required)
|
||||
|
||||
### Build fails on macOS
|
||||
Make sure virtual environment is active:
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
```
|
||||
45
flash_tool/ESP32_Flasher_Windows.spec
Normal file
45
flash_tool/ESP32_Flasher_Windows.spec
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec file for Windows executable
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['gui_flasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('requirements.txt', '.')],
|
||||
hiddenimports=['serial.tools.list_ports'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='ESP32_Flasher',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False, # Set to False for windowed mode (no console)
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=None, # Add path to .ico file if you have one
|
||||
)
|
||||
56
flash_tool/ESP32_Flasher_macOS.spec
Normal file
56
flash_tool/ESP32_Flasher_macOS.spec
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec file for macOS executable
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['gui_flasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('requirements.txt', '.')],
|
||||
hiddenimports=['serial.tools.list_ports'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='ESP32_Flasher',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False, # macOS app bundle (no console window)
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
|
||||
# Create macOS app bundle
|
||||
app = BUNDLE(
|
||||
exe,
|
||||
name='ESP32_Flasher.app',
|
||||
icon=None, # Add path to .icns file if you have one
|
||||
bundle_identifier='com.soundshot.esp32flasher',
|
||||
info_plist={
|
||||
'NSPrincipalClass': 'NSApplication',
|
||||
'NSHighResolutionCapable': 'True',
|
||||
},
|
||||
)
|
||||
81
flash_tool/ESP32_WebFlasher_Windows.spec
Normal file
81
flash_tool/ESP32_WebFlasher_Windows.spec
Normal file
@@ -0,0 +1,81 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec file for Windows executable (Web-based UI)
|
||||
|
||||
block_cipher = None
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Find esptool installation dynamically
|
||||
try:
|
||||
import esptool
|
||||
esptool_dir = os.path.dirname(esptool.__file__)
|
||||
print(f"Found esptool at: {esptool_dir}")
|
||||
except ImportError:
|
||||
print("Warning: esptool not found, stub files will not be included")
|
||||
esptool_dir = None
|
||||
|
||||
# Build datas list - include all esptool data files
|
||||
datas_list = [
|
||||
('templates', 'templates'),
|
||||
('static', 'static'),
|
||||
]
|
||||
|
||||
# Add esptool targets directory (includes stub_flasher JSON files)
|
||||
if esptool_dir and os.path.exists(os.path.join(esptool_dir, 'targets')):
|
||||
targets_dir = os.path.join(esptool_dir, 'targets')
|
||||
datas_list.append((targets_dir, 'esptool/targets'))
|
||||
print(f"Including esptool targets from: {targets_dir}")
|
||||
|
||||
a = Analysis(
|
||||
['web_flasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=datas_list,
|
||||
hiddenimports=[
|
||||
'serial.tools.list_ports',
|
||||
'flask',
|
||||
'jinja2',
|
||||
'werkzeug',
|
||||
'flask_socketio',
|
||||
'socketio',
|
||||
'engineio.async_drivers.threading',
|
||||
'esptool',
|
||||
'esptool.cmds',
|
||||
'esptool.loader',
|
||||
'esptool.util',
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='ESP32_Flasher',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False, # No console window
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=None, # Add path to .ico file if you have one
|
||||
)
|
||||
69
flash_tool/ESP32_WebFlasher_macOS.spec
Normal file
69
flash_tool/ESP32_WebFlasher_macOS.spec
Normal file
@@ -0,0 +1,69 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
# PyInstaller spec file for macOS executable (Web-based UI)
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['web_flasher.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[
|
||||
('templates', 'templates'),
|
||||
('static', 'static'),
|
||||
],
|
||||
hiddenimports=[
|
||||
'serial.tools.list_ports',
|
||||
'flask',
|
||||
'jinja2',
|
||||
'werkzeug',
|
||||
'flask_socketio',
|
||||
'socketio',
|
||||
'engineio.async_drivers.threading',
|
||||
],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='ESP32_Flasher',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False, # macOS app bundle (no console window)
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
|
||||
# Create macOS app bundle
|
||||
app = BUNDLE(
|
||||
exe,
|
||||
name='ESP32_Flasher.app',
|
||||
icon=None, # Add path to .icns file if you have one
|
||||
bundle_identifier='com.soundshot.esp32flasher',
|
||||
info_plist={
|
||||
'NSPrincipalClass': 'NSApplication',
|
||||
'NSHighResolutionCapable': 'True',
|
||||
'CFBundleShortVersionString': '1.0.0',
|
||||
'CFBundleDisplayName': 'ESP32 Flasher',
|
||||
},
|
||||
)
|
||||
148
flash_tool/README.md
Normal file
148
flash_tool/README.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# ESP32 Firmware Flasher GUI
|
||||
|
||||
A cross-platform GUI tool for flashing ESP32 firmware packages.
|
||||
|
||||
## Features
|
||||
|
||||
- Simple, user-friendly graphical interface
|
||||
- Cross-platform support (Windows, macOS, Linux)
|
||||
- Automatic serial port detection
|
||||
- Firmware package validation (.zip files)
|
||||
- Real-time flashing progress output
|
||||
- Configurable flash parameters
|
||||
|
||||
## Building Executables
|
||||
|
||||
### Universal Build (Recommended)
|
||||
|
||||
The easiest way to build for your current platform:
|
||||
|
||||
```bash
|
||||
python build.py
|
||||
```
|
||||
|
||||
This script will:
|
||||
1. Detect your platform automatically
|
||||
2. Install required dependencies
|
||||
3. Build the appropriate executable
|
||||
|
||||
### Platform-Specific Builds
|
||||
|
||||
#### macOS
|
||||
|
||||
```bash
|
||||
./build_macos.sh
|
||||
```
|
||||
|
||||
This creates an application bundle at `dist/ESP32_Flasher.app`
|
||||
|
||||
To run:
|
||||
```bash
|
||||
open dist/ESP32_Flasher.app
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
```batch
|
||||
build_from_spec.bat
|
||||
```
|
||||
|
||||
This creates an executable at `dist\ESP32_Flasher.exe`
|
||||
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
python build.py
|
||||
```
|
||||
|
||||
Creates an executable at `dist/ESP32_Flasher`
|
||||
|
||||
## Running Without Building
|
||||
|
||||
You can run the GUI directly with Python:
|
||||
|
||||
```bash
|
||||
python gui_flasher.py
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Firmware Package Format
|
||||
|
||||
The flasher expects a `.zip` file containing these files:
|
||||
- `bootloader.bin` - ESP32 bootloader
|
||||
- `partition-table.bin` - Partition table
|
||||
- `ota_data_initial.bin` - OTA data
|
||||
- `soundshot.bin` - Main application firmware
|
||||
|
||||
## Usage
|
||||
|
||||
1. Launch the ESP32_Flasher application
|
||||
2. Select your ESP32's serial port (or click Refresh)
|
||||
3. Browse and select your firmware `.zip` package
|
||||
4. (Optional) Adjust flash settings if needed
|
||||
5. Click "Flash Firmware"
|
||||
6. Wait for the process to complete
|
||||
|
||||
## Flash Settings
|
||||
|
||||
Default settings work for most ESP32 boards:
|
||||
- **Chip**: esp32
|
||||
- **Baud Rate**: 460800 (faster) or 115200 (more reliable)
|
||||
- **Flash Mode**: dio
|
||||
- **Flash Freq**: 40m
|
||||
- **Flash Size**: 2MB (adjust based on your board)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port Not Detected
|
||||
- Ensure ESP32 is connected via USB
|
||||
- Install CH340/CP2102 drivers if needed (Windows/macOS)
|
||||
- On Linux, add user to `dialout` group: `sudo usermod -a -G dialout $USER`
|
||||
|
||||
### Flash Failed
|
||||
- Try lower baud rate (115200)
|
||||
- Press and hold BOOT button during flash
|
||||
- Check USB cable quality
|
||||
- Verify firmware package integrity
|
||||
|
||||
### macOS: "App is damaged"
|
||||
Run this command to allow the app:
|
||||
```bash
|
||||
xattr -cr dist/ESP32_Flasher.app
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
flash_tool/
|
||||
├── gui_flasher.py # Main GUI application
|
||||
├── ESP32_Flasher_macOS.spec # PyInstaller spec for macOS
|
||||
├── ESP32_Flasher_Windows.spec # PyInstaller spec for Windows
|
||||
├── build.py # Universal build script
|
||||
├── build_macos.sh # macOS build script
|
||||
├── build_from_spec.bat # Windows build script
|
||||
├── requirements.txt # Python dependencies
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **tkinter**: GUI framework (included with Python)
|
||||
- **pyserial**: Serial port communication
|
||||
- **esptool**: ESP32 flashing utility
|
||||
- **pyinstaller**: Executable builder
|
||||
|
||||
## License
|
||||
|
||||
[Your license here]
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions, please contact [your contact info].
|
||||
BIN
flash_tool/SoundShot.png
Normal file
BIN
flash_tool/SoundShot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
171
flash_tool/build.py
Executable file
171
flash_tool/build.py
Executable file
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Universal build script for ESP32 Flasher GUI
|
||||
Automatically detects platform and builds the appropriate executable
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
import os
|
||||
|
||||
def get_platform():
|
||||
"""Detect the current platform"""
|
||||
system = platform.system()
|
||||
if system == "Darwin":
|
||||
return "macos"
|
||||
elif system == "Windows":
|
||||
return "windows"
|
||||
elif system == "Linux":
|
||||
return "linux"
|
||||
else:
|
||||
return "unknown"
|
||||
|
||||
def setup_venv():
|
||||
"""Create and setup virtual environment if on macOS"""
|
||||
if platform.system() == "Darwin":
|
||||
if not os.path.exists("venv"):
|
||||
print("Creating virtual environment (required on macOS)...")
|
||||
try:
|
||||
subprocess.run([sys.executable, "-m", "venv", "venv"], check=True)
|
||||
print("✓ Virtual environment created")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Failed to create virtual environment: {e}")
|
||||
return False
|
||||
|
||||
# Use venv Python for the rest of the script
|
||||
venv_python = os.path.join("venv", "bin", "python3")
|
||||
if os.path.exists(venv_python):
|
||||
return venv_python
|
||||
|
||||
return sys.executable
|
||||
|
||||
def install_dependencies(python_exe=None):
|
||||
"""Install required Python packages"""
|
||||
if python_exe is None:
|
||||
python_exe = sys.executable
|
||||
|
||||
print("Installing dependencies...")
|
||||
packages = ["pyinstaller", "esptool", "pyserial"]
|
||||
|
||||
try:
|
||||
subprocess.run([python_exe, "-m", "pip", "install"] + packages, check=True)
|
||||
print("✓ Dependencies installed successfully")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Failed to install dependencies: {e}")
|
||||
return False
|
||||
|
||||
def build_macos():
|
||||
"""Build macOS application bundle"""
|
||||
print("\n=== Building for macOS ===")
|
||||
spec_file = "ESP32_Flasher_macOS.spec"
|
||||
|
||||
if not os.path.exists(spec_file):
|
||||
print(f"✗ Error: {spec_file} not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
subprocess.run(["pyinstaller", spec_file], check=True)
|
||||
|
||||
if os.path.exists("dist/ESP32_Flasher.app"):
|
||||
print("\n✓ Build complete!")
|
||||
print(f"\nApplication created at: dist/ESP32_Flasher.app")
|
||||
print("\nTo run the application:")
|
||||
print(" open dist/ESP32_Flasher.app")
|
||||
return True
|
||||
else:
|
||||
print("\n✗ Build failed - application not found in dist folder")
|
||||
return False
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"\n✗ Build failed: {e}")
|
||||
return False
|
||||
|
||||
def build_windows():
|
||||
"""Build Windows executable"""
|
||||
print("\n=== Building for Windows ===")
|
||||
spec_file = "ESP32_Flasher_Windows.spec"
|
||||
|
||||
if not os.path.exists(spec_file):
|
||||
print(f"✗ Error: {spec_file} not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
subprocess.run(["pyinstaller", spec_file], check=True)
|
||||
|
||||
if os.path.exists("dist/ESP32_Flasher.exe"):
|
||||
print("\n✓ Build complete!")
|
||||
print(f"\nExecutable created at: dist\\ESP32_Flasher.exe")
|
||||
print("\nYou can now distribute ESP32_Flasher.exe to users.")
|
||||
return True
|
||||
else:
|
||||
print("\n✗ Build failed - executable not found in dist folder")
|
||||
return False
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"\n✗ Build failed: {e}")
|
||||
return False
|
||||
|
||||
def build_linux():
|
||||
"""Build Linux executable"""
|
||||
print("\n=== Building for Linux ===")
|
||||
print("Note: Linux users typically prefer to run Python scripts directly.")
|
||||
print("Creating standalone executable anyway...")
|
||||
|
||||
# Use macOS spec as template for Linux (both use ELF format)
|
||||
spec_file = "ESP32_Flasher_macOS.spec"
|
||||
|
||||
if not os.path.exists(spec_file):
|
||||
print(f"✗ Error: {spec_file} not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
subprocess.run(["pyinstaller", spec_file], check=True)
|
||||
|
||||
if os.path.exists("dist/ESP32_Flasher"):
|
||||
print("\n✓ Build complete!")
|
||||
print(f"\nExecutable created at: dist/ESP32_Flasher")
|
||||
print("\nTo run:")
|
||||
print(" ./dist/ESP32_Flasher")
|
||||
return True
|
||||
else:
|
||||
print("\n✗ Build failed - executable not found in dist folder")
|
||||
return False
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"\n✗ Build failed: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main build function"""
|
||||
print("=== ESP32 Flasher - Universal Build Script ===\n")
|
||||
|
||||
# Detect platform
|
||||
current_platform = get_platform()
|
||||
print(f"Detected platform: {current_platform}")
|
||||
|
||||
if current_platform == "unknown":
|
||||
print("✗ Unsupported platform")
|
||||
return 1
|
||||
|
||||
# Setup virtual environment if needed (macOS)
|
||||
python_exe = setup_venv()
|
||||
|
||||
# Install dependencies
|
||||
if not install_dependencies(python_exe):
|
||||
return 1
|
||||
|
||||
# Build for detected platform
|
||||
success = False
|
||||
if current_platform == "macos":
|
||||
success = build_macos()
|
||||
elif current_platform == "windows":
|
||||
success = build_windows()
|
||||
elif current_platform == "linux":
|
||||
success = build_linux()
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,11 +1,24 @@
|
||||
@echo off
|
||||
echo === ESP32 Flasher - Windows Build Script ===
|
||||
echo.
|
||||
|
||||
echo Installing dependencies...
|
||||
pip install pyinstaller esptool pyserial
|
||||
|
||||
echo.
|
||||
echo Building executable from spec file...
|
||||
pyinstaller ESP32_Flasher.spec
|
||||
echo Building Windows executable...
|
||||
pyinstaller ESP32_Flasher_Windows.spec
|
||||
|
||||
echo.
|
||||
echo Build complete! Find ESP32_Flasher.exe in the dist folder.
|
||||
if exist "dist\ESP32_Flasher.exe" (
|
||||
echo Build complete!
|
||||
echo.
|
||||
echo Executable created at: dist\ESP32_Flasher.exe
|
||||
echo.
|
||||
echo You can now distribute ESP32_Flasher.exe to users.
|
||||
) else (
|
||||
echo Build failed - executable not found in dist folder
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
45
flash_tool/build_macos.sh
Executable file
45
flash_tool/build_macos.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# Build script for macOS executable
|
||||
|
||||
echo "=== ESP32 Flasher - macOS Build Script ==="
|
||||
echo ""
|
||||
|
||||
# Check if running on macOS
|
||||
if [[ "$OSTYPE" != "darwin"* ]]; then
|
||||
echo "❌ Error: This script is for macOS only."
|
||||
echo " Use build_from_spec.bat on Windows."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create virtual environment if it doesn't exist
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
python3 -m venv venv
|
||||
fi
|
||||
|
||||
echo "Activating virtual environment..."
|
||||
source venv/bin/activate
|
||||
|
||||
echo "Installing dependencies..."
|
||||
pip install pyinstaller esptool pyserial
|
||||
|
||||
echo ""
|
||||
echo "Building macOS application bundle..."
|
||||
pyinstaller ESP32_Flasher_macOS.spec
|
||||
|
||||
echo ""
|
||||
if [ -d "dist/ESP32_Flasher.app" ]; then
|
||||
echo "✓ Build complete!"
|
||||
echo ""
|
||||
echo "Application created at: dist/ESP32_Flasher.app"
|
||||
echo ""
|
||||
echo "To run the application:"
|
||||
echo " open dist/ESP32_Flasher.app"
|
||||
echo ""
|
||||
echo "To distribute, you can:"
|
||||
echo " 1. Zip the .app bundle"
|
||||
echo " 2. Create a DMG installer (requires additional tools)"
|
||||
else
|
||||
echo "❌ Build failed - application not found in dist folder"
|
||||
exit 1
|
||||
fi
|
||||
43
flash_tool/build_web_macos.sh
Executable file
43
flash_tool/build_web_macos.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# Build script for macOS executable (Web-based UI)
|
||||
|
||||
echo "=== ESP32 Flasher (Web UI) - macOS Build Script ==="
|
||||
echo ""
|
||||
|
||||
# Check if running on macOS
|
||||
if [[ "$OSTYPE" != "darwin"* ]]; then
|
||||
echo "❌ Error: This script is for macOS only."
|
||||
echo " Use build_web_windows.bat on Windows."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create virtual environment if it doesn't exist
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "Creating virtual environment..."
|
||||
python3 -m venv venv
|
||||
fi
|
||||
|
||||
echo "Activating virtual environment..."
|
||||
source venv/bin/activate
|
||||
|
||||
echo "Installing dependencies..."
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo ""
|
||||
echo "Building macOS application bundle..."
|
||||
pyinstaller ESP32_WebFlasher_macOS.spec
|
||||
|
||||
echo ""
|
||||
if [ -d "dist/ESP32_Flasher.app" ]; then
|
||||
echo "✓ Build complete!"
|
||||
echo ""
|
||||
echo "Application created at: dist/ESP32_Flasher.app"
|
||||
echo ""
|
||||
echo "To run the application:"
|
||||
echo " open dist/ESP32_Flasher.app"
|
||||
echo ""
|
||||
echo "The app will open in your default web browser!"
|
||||
else
|
||||
echo "❌ Build failed - application not found in dist folder"
|
||||
exit 1
|
||||
fi
|
||||
32
flash_tool/build_web_windows.bat
Normal file
32
flash_tool/build_web_windows.bat
Normal file
@@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
echo === ESP32 Flasher (Web UI) - Windows Build Script ===
|
||||
echo.
|
||||
|
||||
REM Check if running on Windows
|
||||
if not "%OS%"=="Windows_NT" (
|
||||
echo Error: This script is for Windows only.
|
||||
echo Use build_web_macos.sh on macOS.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Installing dependencies...
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo.
|
||||
echo Building Windows executable...
|
||||
pyinstaller ESP32_WebFlasher_Windows.spec
|
||||
|
||||
echo.
|
||||
if exist "dist\ESP32_Flasher.exe" (
|
||||
echo Build complete!
|
||||
echo.
|
||||
echo Executable created at: dist\ESP32_Flasher.exe
|
||||
echo.
|
||||
echo The app will open in your default web browser!
|
||||
echo You can now distribute ESP32_Flasher.exe to users.
|
||||
) else (
|
||||
echo Build failed - executable not found in dist folder
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -21,6 +21,9 @@ class ESP32FlasherGUI:
|
||||
self.root.title("ESP32 Firmware Flasher")
|
||||
self.root.geometry("600x500")
|
||||
|
||||
# Configure colors for dark mode compatibility
|
||||
self.setup_colors()
|
||||
|
||||
# Variables
|
||||
self.port_var = tk.StringVar()
|
||||
self.firmware_path_var = tk.StringVar()
|
||||
@@ -29,9 +32,41 @@ class ESP32FlasherGUI:
|
||||
self.setup_ui()
|
||||
self.refresh_ports()
|
||||
|
||||
def setup_colors(self):
|
||||
"""Configure colors that work in both light and dark mode"""
|
||||
# Try to detect dark mode on macOS
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
['defaults', 'read', '-g', 'AppleInterfaceStyle'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
is_dark_mode = (result.returncode == 0 and 'Dark' in result.stdout)
|
||||
except:
|
||||
is_dark_mode = False
|
||||
|
||||
if is_dark_mode:
|
||||
# Dark mode colors
|
||||
self.bg_color = '#2b2b2b'
|
||||
self.fg_color = '#ffffff'
|
||||
self.text_bg = '#1e1e1e'
|
||||
self.text_fg = '#d4d4d4'
|
||||
self.button_bg = '#3c3c3c'
|
||||
else:
|
||||
# Light mode colors
|
||||
self.bg_color = '#f0f0f0'
|
||||
self.fg_color = '#000000'
|
||||
self.text_bg = '#ffffff'
|
||||
self.text_fg = '#000000'
|
||||
self.button_bg = '#e0e0e0'
|
||||
|
||||
# Configure root window
|
||||
self.root.configure(bg=self.bg_color)
|
||||
|
||||
def setup_ui(self):
|
||||
# Main frame
|
||||
main_frame = ttk.Frame(self.root, padding="10")
|
||||
main_frame = tk.Frame(self.root, bg=self.bg_color, padx=10, pady=10)
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# Configure grid weights
|
||||
@@ -40,55 +75,76 @@ class ESP32FlasherGUI:
|
||||
main_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Port selection
|
||||
ttk.Label(main_frame, text="Serial Port:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
port_frame = ttk.Frame(main_frame)
|
||||
tk.Label(main_frame, text="Serial Port:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=0, column=0, sticky=tk.W, pady=5)
|
||||
port_frame = tk.Frame(main_frame, bg=self.bg_color)
|
||||
port_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
port_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, state="readonly")
|
||||
self.port_combo.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
ttk.Button(port_frame, text="Refresh", command=self.refresh_ports).grid(row=0, column=1)
|
||||
tk.Button(port_frame, text="Refresh", command=self.refresh_ports,
|
||||
bg=self.button_bg, fg=self.fg_color, relief=tk.RAISED).grid(row=0, column=1)
|
||||
|
||||
# Firmware package selection
|
||||
ttk.Label(main_frame, text="Firmware Package:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
firmware_frame = ttk.Frame(main_frame)
|
||||
tk.Label(main_frame, text="Firmware Package:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=1, column=0, sticky=tk.W, pady=5)
|
||||
firmware_frame = tk.Frame(main_frame, bg=self.bg_color)
|
||||
firmware_frame.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
firmware_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.firmware_entry = ttk.Entry(firmware_frame, textvariable=self.firmware_path_var, state="readonly")
|
||||
self.firmware_entry = tk.Entry(firmware_frame, textvariable=self.firmware_path_var,
|
||||
state="readonly", bg=self.text_bg, fg=self.text_fg,
|
||||
relief=tk.SUNKEN, borderwidth=2)
|
||||
self.firmware_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
ttk.Button(firmware_frame, text="Browse", command=self.browse_firmware).grid(row=0, column=1)
|
||||
tk.Button(firmware_frame, text="Browse", command=self.browse_firmware,
|
||||
bg=self.button_bg, fg=self.fg_color, relief=tk.RAISED).grid(row=0, column=1)
|
||||
|
||||
# Flash settings frame
|
||||
settings_frame = ttk.LabelFrame(main_frame, text="Flash Settings", padding="5")
|
||||
settings_frame = tk.LabelFrame(main_frame, text="Flash Settings",
|
||||
bg=self.bg_color, fg=self.fg_color,
|
||||
relief=tk.GROOVE, borderwidth=2, padx=5, pady=5)
|
||||
settings_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10)
|
||||
settings_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Flash settings
|
||||
ttk.Label(settings_frame, text="Chip:").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||
tk.Label(settings_frame, text="Chip:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=0, column=0, sticky=tk.W, pady=2, padx=(0, 10))
|
||||
self.chip_var = tk.StringVar(value="esp32")
|
||||
ttk.Entry(settings_frame, textvariable=self.chip_var, width=15).grid(row=0, column=1, sticky=tk.W, pady=2)
|
||||
tk.Entry(settings_frame, textvariable=self.chip_var, width=15,
|
||||
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=0, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Baud Rate:").grid(row=1, column=0, sticky=tk.W, pady=2)
|
||||
tk.Label(settings_frame, text="Baud Rate:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=1, column=0, sticky=tk.W, pady=2, padx=(0, 10))
|
||||
self.baud_var = tk.StringVar(value="460800")
|
||||
ttk.Entry(settings_frame, textvariable=self.baud_var, width=15).grid(row=1, column=1, sticky=tk.W, pady=2)
|
||||
tk.Entry(settings_frame, textvariable=self.baud_var, width=15,
|
||||
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=1, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Mode:").grid(row=2, column=0, sticky=tk.W, pady=2)
|
||||
tk.Label(settings_frame, text="Flash Mode:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=2, column=0, sticky=tk.W, pady=2, padx=(0, 10))
|
||||
self.flash_mode_var = tk.StringVar(value="dio")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_mode_var, width=15).grid(row=2, column=1, sticky=tk.W, pady=2)
|
||||
tk.Entry(settings_frame, textvariable=self.flash_mode_var, width=15,
|
||||
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=2, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Freq:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||
tk.Label(settings_frame, text="Flash Freq:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=3, column=0, sticky=tk.W, pady=2, padx=(0, 10))
|
||||
self.flash_freq_var = tk.StringVar(value="40m")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_freq_var, width=15).grid(row=3, column=1, sticky=tk.W, pady=2)
|
||||
tk.Entry(settings_frame, textvariable=self.flash_freq_var, width=15,
|
||||
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=3, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
ttk.Label(settings_frame, text="Flash Size:").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||
tk.Label(settings_frame, text="Flash Size:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=4, column=0, sticky=tk.W, pady=2, padx=(0, 10))
|
||||
self.flash_size_var = tk.StringVar(value="2MB")
|
||||
ttk.Entry(settings_frame, textvariable=self.flash_size_var, width=15).grid(row=4, column=1, sticky=tk.W, pady=2)
|
||||
tk.Entry(settings_frame, textvariable=self.flash_size_var, width=15,
|
||||
bg=self.text_bg, fg=self.text_fg, relief=tk.SUNKEN).grid(row=4, column=1, sticky=tk.W, pady=2)
|
||||
|
||||
# Flash button
|
||||
self.flash_button = ttk.Button(main_frame, text="Flash Firmware", command=self.flash_firmware)
|
||||
self.flash_button = tk.Button(main_frame, text="Flash Firmware", command=self.flash_firmware,
|
||||
bg=self.button_bg, fg=self.fg_color,
|
||||
relief=tk.RAISED, padx=20, pady=5,
|
||||
font=('TkDefaultFont', 10, 'bold'))
|
||||
self.flash_button.grid(row=3, column=0, columnspan=2, pady=10)
|
||||
|
||||
# Progress bar
|
||||
@@ -96,8 +152,18 @@ class ESP32FlasherGUI:
|
||||
self.progress.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
|
||||
|
||||
# Log output
|
||||
ttk.Label(main_frame, text="Output:").grid(row=5, column=0, sticky=tk.W, pady=(10, 0))
|
||||
self.log_text = scrolledtext.ScrolledText(main_frame, height=15, width=70)
|
||||
tk.Label(main_frame, text="Output:", bg=self.bg_color, fg=self.fg_color).grid(
|
||||
row=5, column=0, sticky=tk.W, pady=(10, 0))
|
||||
self.log_text = scrolledtext.ScrolledText(
|
||||
main_frame,
|
||||
height=15,
|
||||
width=70,
|
||||
bg=self.text_bg,
|
||||
fg=self.text_fg,
|
||||
insertbackground=self.text_fg, # Cursor color
|
||||
relief=tk.SUNKEN,
|
||||
borderwidth=2
|
||||
)
|
||||
self.log_text.grid(row=6, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||
main_frame.rowconfigure(6, weight=1)
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
esptool>=4.0
|
||||
pyserial>=3.5
|
||||
pyinstaller>=5.0
|
||||
flask>=2.3.0
|
||||
flask-socketio>=5.3.0
|
||||
python-socketio>=5.11.0
|
||||
BIN
flash_tool/static/SoundShot.png
Normal file
BIN
flash_tool/static/SoundShot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
570
flash_tool/templates/index.html
Normal file
570
flash_tool/templates/index.html
Normal file
@@ -0,0 +1,570 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SoundShot Firmware Flasher</title>
|
||||
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #d94125 0%, #a33318 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
max-width: 700px;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background: linear-gradient(135deg, #2c2c2c 0%, #1a1a1a 100%);
|
||||
padding: 40px 20px 30px 20px;
|
||||
margin: -40px -40px 30px -40px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #c04026;
|
||||
margin-bottom: 10px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #d0d0d0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: #c04026;
|
||||
}
|
||||
|
||||
.file-input-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-input-wrapper input[type="file"] {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
.file-input-label {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
border: 2px dashed #e0e0e0;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.file-input-label:hover {
|
||||
border-color: #c04026;
|
||||
background: #fef6f4;
|
||||
}
|
||||
|
||||
.file-selected {
|
||||
border-color: #c04026;
|
||||
border-style: solid;
|
||||
background: #fef6f4;
|
||||
color: #c04026;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #d94125 0%, #a33318 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(217, 65, 37, 0.3);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.output-section {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.output-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.output-toggle {
|
||||
background: #f0f0f0;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.output-toggle:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.output-box {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.output-box.expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.output-box:empty::before {
|
||||
content: 'Output will appear here...';
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin: 20px 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-bar.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #d94125, #a33318);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease-out;
|
||||
}
|
||||
|
||||
.progress-bar-fill.indeterminate {
|
||||
animation: progress 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
0% { width: 0%; }
|
||||
50% { width: 100%; }
|
||||
100% { width: 0%; }
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #c04026;
|
||||
font-weight: 600;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-text.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-message.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.status-message.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
padding: 8px 16px;
|
||||
background: #f0f0f0;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
margin-left: 10px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.port-section {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.port-section .form-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="/static/SoundShot.png" alt="SoundShot" class="logo">
|
||||
<p class="subtitle">Firmware Flasher</p>
|
||||
</div>
|
||||
|
||||
<form id="flashForm" onsubmit="return false;">
|
||||
<div class="port-section">
|
||||
<div class="form-group">
|
||||
<label for="port">Serial Port</label>
|
||||
<select id="port" name="port" required>
|
||||
<option value="">Select a port...</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="refresh-btn" onclick="refreshPorts()">🔄 Refresh</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="firmware">Firmware Package (.zip)</label>
|
||||
<div class="file-input-wrapper">
|
||||
<input type="file" id="firmware" name="firmware" accept=".zip" required>
|
||||
<label for="firmware" class="file-input-label" id="fileLabel">
|
||||
📦 Click to select firmware package
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden fields with default values -->
|
||||
<input type="hidden" id="chip" name="chip" value="esp32">
|
||||
<input type="hidden" id="baud" name="baud" value="460800">
|
||||
<input type="hidden" id="flash_mode" name="flash_mode" value="dio">
|
||||
<input type="hidden" id="flash_freq" name="flash_freq" value="40m">
|
||||
<input type="hidden" id="flash_size" name="flash_size" value="2MB">
|
||||
|
||||
<button type="submit" class="btn btn-primary" id="flashBtn">
|
||||
⚡ Flash Firmware
|
||||
</button>
|
||||
|
||||
<div class="progress-bar" id="progressBar">
|
||||
<div class="progress-bar-fill" id="progressBarFill"></div>
|
||||
</div>
|
||||
<div class="progress-text" id="progressText">0%</div>
|
||||
|
||||
<div class="status-message" id="statusMessage"></div>
|
||||
</form>
|
||||
|
||||
<div class="output-section">
|
||||
<div class="output-header">
|
||||
<label>Output Log</label>
|
||||
<button type="button" class="output-toggle" id="outputToggle" onclick="toggleOutput()">▼ Show Details</button>
|
||||
</div>
|
||||
<div class="output-box" id="output"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let statusCheckInterval = null;
|
||||
let socket = null;
|
||||
let heartbeatInterval = null;
|
||||
|
||||
// Toggle output visibility
|
||||
function toggleOutput() {
|
||||
const output = document.getElementById('output');
|
||||
const toggle = document.getElementById('outputToggle');
|
||||
|
||||
if (output.classList.contains('expanded')) {
|
||||
output.classList.remove('expanded');
|
||||
toggle.textContent = '▼ Show Details';
|
||||
} else {
|
||||
output.classList.add('expanded');
|
||||
toggle.textContent = '▲ Hide Details';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Socket.IO connection
|
||||
function initWebSocket() {
|
||||
socket = io();
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('WebSocket connected');
|
||||
|
||||
// Start sending heartbeats every 2 seconds
|
||||
heartbeatInterval = setInterval(() => {
|
||||
socket.emit('heartbeat');
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('WebSocket disconnected');
|
||||
if (heartbeatInterval) {
|
||||
clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = null;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('heartbeat_ack', (data) => {
|
||||
// Heartbeat acknowledged
|
||||
});
|
||||
}
|
||||
|
||||
// Load ports on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initWebSocket();
|
||||
refreshPorts();
|
||||
});
|
||||
|
||||
// Clean up on page unload
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (socket) {
|
||||
socket.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle file selection
|
||||
document.getElementById('firmware').addEventListener('change', function(e) {
|
||||
const fileLabel = document.getElementById('fileLabel');
|
||||
if (e.target.files.length > 0) {
|
||||
fileLabel.textContent = '✓ ' + e.target.files[0].name;
|
||||
fileLabel.classList.add('file-selected');
|
||||
} else {
|
||||
fileLabel.textContent = '📦 Click to select firmware package';
|
||||
fileLabel.classList.remove('file-selected');
|
||||
}
|
||||
});
|
||||
|
||||
async function refreshPorts() {
|
||||
const portSelect = document.getElementById('port');
|
||||
portSelect.innerHTML = '<option value="">Loading ports...</option>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ports');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
portSelect.innerHTML = '<option value="">Select a port...</option>';
|
||||
data.ports.forEach(port => {
|
||||
const option = document.createElement('option');
|
||||
option.value = port.device;
|
||||
option.textContent = `${port.device} - ${port.description}`;
|
||||
portSelect.appendChild(option);
|
||||
});
|
||||
} else {
|
||||
portSelect.innerHTML = '<option value="">Error loading ports</option>';
|
||||
}
|
||||
} catch (error) {
|
||||
portSelect.innerHTML = '<option value="">Error loading ports</option>';
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('flashForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
const flashBtn = document.getElementById('flashBtn');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressBarFill = document.getElementById('progressBarFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
const output = document.getElementById('output');
|
||||
const outputToggle = document.getElementById('outputToggle');
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
|
||||
// Disable form
|
||||
flashBtn.disabled = true;
|
||||
flashBtn.textContent = '⚡ Flashing...';
|
||||
progressBar.classList.add('active');
|
||||
progressBarFill.classList.add('indeterminate');
|
||||
progressBarFill.style.width = '0%';
|
||||
progressText.classList.add('active');
|
||||
progressText.textContent = '0%';
|
||||
output.textContent = '';
|
||||
output.classList.remove('expanded');
|
||||
outputToggle.textContent = '▼ Show Details';
|
||||
statusMessage.style.display = 'none';
|
||||
statusMessage.className = 'status-message';
|
||||
|
||||
// Create FormData
|
||||
const formData = new FormData(e.target);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/flash', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Start checking status
|
||||
statusCheckInterval = setInterval(checkStatus, 500);
|
||||
} else {
|
||||
showError(data.error);
|
||||
resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Failed to start flash: ' + error.message);
|
||||
resetForm();
|
||||
}
|
||||
});
|
||||
|
||||
async function checkStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/status');
|
||||
const data = await response.json();
|
||||
|
||||
// Update output
|
||||
const output = document.getElementById('output');
|
||||
output.textContent = data.output.join('\n');
|
||||
output.scrollTop = output.scrollHeight;
|
||||
|
||||
// Update progress bar
|
||||
const progressBarFill = document.getElementById('progressBarFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
if (data.progress !== undefined && data.progress > 0) {
|
||||
// Switch from indeterminate to determinate
|
||||
progressBarFill.classList.remove('indeterminate');
|
||||
progressBarFill.style.width = data.progress + '%';
|
||||
progressText.textContent = data.progress.toFixed(1) + '%';
|
||||
}
|
||||
|
||||
// Check if complete
|
||||
if (!data.running && statusCheckInterval) {
|
||||
clearInterval(statusCheckInterval);
|
||||
statusCheckInterval = null;
|
||||
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
if (data.success === true) {
|
||||
progressBarFill.style.width = '100%';
|
||||
progressText.textContent = '100%';
|
||||
statusMessage.textContent = '✓ Flash completed successfully!';
|
||||
statusMessage.className = 'status-message success';
|
||||
statusMessage.style.display = 'block';
|
||||
} else if (data.success === false) {
|
||||
statusMessage.textContent = '✗ Flash operation failed. Check the output log for details.';
|
||||
statusMessage.className = 'status-message error';
|
||||
statusMessage.style.display = 'block';
|
||||
}
|
||||
|
||||
resetForm();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking status:', error);
|
||||
clearInterval(statusCheckInterval);
|
||||
statusCheckInterval = null;
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
statusMessage.textContent = '✗ ' + message;
|
||||
statusMessage.className = 'status-message error';
|
||||
statusMessage.style.display = 'block';
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
const flashBtn = document.getElementById('flashBtn');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressBarFill = document.getElementById('progressBarFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
flashBtn.disabled = false;
|
||||
flashBtn.textContent = '⚡ Flash Firmware';
|
||||
|
||||
// Delay hiding progress to let user see completion
|
||||
setTimeout(() => {
|
||||
progressBar.classList.remove('active');
|
||||
progressText.classList.remove('active');
|
||||
progressBarFill.classList.remove('indeterminate');
|
||||
progressBarFill.style.width = '0%';
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
343
flash_tool/web_flasher.py
Normal file
343
flash_tool/web_flasher.py
Normal file
@@ -0,0 +1,343 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ESP32 Firmware Flash Web Server
|
||||
Web-based GUI interface for flashing firmware packages to ESP32 devices
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, request, jsonify, send_from_directory
|
||||
from flask_socketio import SocketIO, emit
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import zipfile
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import serial.tools.list_ports
|
||||
import webbrowser
|
||||
import socket
|
||||
import time
|
||||
import signal
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max file size
|
||||
app.config['SECRET_KEY'] = 'esp32-flasher-secret-key'
|
||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
|
||||
# Global state
|
||||
flash_status = {
|
||||
'running': False,
|
||||
'output': [],
|
||||
'success': None,
|
||||
'progress': 0
|
||||
}
|
||||
|
||||
# Client connection tracking
|
||||
client_connected = False
|
||||
last_heartbeat = time.time()
|
||||
shutdown_timer = None
|
||||
|
||||
def get_free_port():
|
||||
"""Find a free port to run the server on"""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(('', 0))
|
||||
s.listen(1)
|
||||
port = s.getsockname()[1]
|
||||
return port
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Serve the main page"""
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/static/<path:filename>')
|
||||
def serve_static(filename):
|
||||
"""Serve static files"""
|
||||
static_dir = os.path.join(os.path.dirname(__file__), 'static')
|
||||
return send_from_directory(static_dir, filename)
|
||||
|
||||
@app.route('/api/ports')
|
||||
def get_ports():
|
||||
"""Get list of available serial ports"""
|
||||
try:
|
||||
ports = [{'device': port.device, 'description': port.description}
|
||||
for port in serial.tools.list_ports.comports()]
|
||||
return jsonify({'success': True, 'ports': ports})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
|
||||
@app.route('/api/flash', methods=['POST'])
|
||||
def flash_firmware():
|
||||
"""Flash firmware to ESP32"""
|
||||
global flash_status
|
||||
|
||||
if flash_status['running']:
|
||||
return jsonify({'success': False, 'error': 'Flash operation already in progress'})
|
||||
|
||||
# Get form data
|
||||
port = request.form.get('port')
|
||||
chip = request.form.get('chip', 'esp32')
|
||||
baud = request.form.get('baud', '460800')
|
||||
flash_mode = request.form.get('flash_mode', 'dio')
|
||||
flash_freq = request.form.get('flash_freq', '40m')
|
||||
flash_size = request.form.get('flash_size', '2MB')
|
||||
|
||||
# Get uploaded file
|
||||
if 'firmware' not in request.files:
|
||||
return jsonify({'success': False, 'error': 'No firmware file uploaded'})
|
||||
|
||||
firmware_file = request.files['firmware']
|
||||
if firmware_file.filename == '':
|
||||
return jsonify({'success': False, 'error': 'No firmware file selected'})
|
||||
|
||||
# Save and extract firmware
|
||||
try:
|
||||
# Create temp directory
|
||||
temp_dir = tempfile.mkdtemp(prefix="esp32_flash_")
|
||||
zip_path = os.path.join(temp_dir, 'firmware.zip')
|
||||
firmware_file.save(zip_path)
|
||||
|
||||
# Extract firmware
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_file:
|
||||
zip_file.extractall(temp_dir)
|
||||
|
||||
# Start flash in background thread
|
||||
flash_status = {'running': True, 'output': [], 'success': None, 'progress': 0}
|
||||
thread = threading.Thread(
|
||||
target=flash_worker,
|
||||
args=(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size)
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
return jsonify({'success': True, 'message': 'Flash started'})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
|
||||
@app.route('/api/status')
|
||||
def get_status():
|
||||
"""Get current flash status"""
|
||||
return jsonify(flash_status)
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
"""Handle client connection"""
|
||||
global client_connected, last_heartbeat, shutdown_timer
|
||||
client_connected = True
|
||||
last_heartbeat = time.time()
|
||||
|
||||
# Cancel any pending shutdown
|
||||
if shutdown_timer:
|
||||
shutdown_timer.cancel()
|
||||
shutdown_timer = None
|
||||
|
||||
print('Client connected')
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def handle_disconnect():
|
||||
"""Handle client disconnection"""
|
||||
global client_connected, shutdown_timer
|
||||
client_connected = False
|
||||
print('Client disconnected - scheduling shutdown in 1 second...')
|
||||
|
||||
# Schedule shutdown after 5 seconds
|
||||
shutdown_timer = threading.Timer(1.0, shutdown_server)
|
||||
shutdown_timer.daemon = True
|
||||
shutdown_timer.start()
|
||||
|
||||
@socketio.on('heartbeat')
|
||||
def handle_heartbeat():
|
||||
"""Handle heartbeat from client"""
|
||||
global last_heartbeat
|
||||
last_heartbeat = time.time()
|
||||
emit('heartbeat_ack', {'timestamp': last_heartbeat})
|
||||
|
||||
def shutdown_server():
|
||||
"""Gracefully shutdown the server"""
|
||||
print('\nNo clients connected. Shutting down server...')
|
||||
|
||||
# Give a moment for any pending operations
|
||||
time.sleep(1)
|
||||
|
||||
# Shutdown Flask
|
||||
try:
|
||||
func = request.environ.get('werkzeug.server.shutdown')
|
||||
if func is None:
|
||||
# Alternative shutdown method
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
else:
|
||||
func()
|
||||
except:
|
||||
# Force exit as last resort
|
||||
os._exit(0)
|
||||
|
||||
def flash_worker(temp_dir, port, chip, baud, flash_mode, flash_freq, flash_size):
|
||||
"""Background worker for flashing firmware"""
|
||||
global flash_status
|
||||
import re
|
||||
import io
|
||||
from contextlib import redirect_stdout, redirect_stderr
|
||||
|
||||
try:
|
||||
# Define file paths
|
||||
bootloader = os.path.join(temp_dir, "bootloader.bin")
|
||||
firmware = os.path.join(temp_dir, "soundshot.bin")
|
||||
ota_initial = os.path.join(temp_dir, "ota_data_initial.bin")
|
||||
partition = os.path.join(temp_dir, "partition-table.bin")
|
||||
|
||||
# Verify files exist
|
||||
required_files = [bootloader, firmware, ota_initial, partition]
|
||||
for file_path in required_files:
|
||||
if not os.path.exists(file_path):
|
||||
flash_status['output'].append(f"ERROR: Missing file {os.path.basename(file_path)}")
|
||||
flash_status['success'] = False
|
||||
flash_status['running'] = False
|
||||
return
|
||||
|
||||
# Build esptool command arguments
|
||||
esptool_args = [
|
||||
"--chip", chip,
|
||||
"--port", port,
|
||||
"--baud", baud,
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write-flash",
|
||||
"--flash-mode", flash_mode,
|
||||
"--flash-freq", flash_freq,
|
||||
"--flash-size", flash_size,
|
||||
"0x1000", bootloader,
|
||||
"0x20000", firmware,
|
||||
"0x11000", ota_initial,
|
||||
"0x8000", partition
|
||||
]
|
||||
|
||||
flash_status['output'].append(f"Running esptool with args: {' '.join(esptool_args)}")
|
||||
flash_status['output'].append("")
|
||||
|
||||
# Import and run esptool directly (works in both script and EXE)
|
||||
import esptool
|
||||
|
||||
# Fix esptool data path for PyInstaller
|
||||
if getattr(sys, 'frozen', False):
|
||||
# Running as compiled executable
|
||||
bundle_dir = sys._MEIPASS
|
||||
esptool_targets = os.path.join(bundle_dir, 'esptool', 'targets')
|
||||
if os.path.exists(esptool_targets):
|
||||
# Override esptool's resource path
|
||||
esptool.RESOURCES_DIR = bundle_dir
|
||||
|
||||
# Capture stdout/stderr
|
||||
output_buffer = io.StringIO()
|
||||
|
||||
# Backup original sys.argv and replace with our arguments
|
||||
original_argv = sys.argv
|
||||
sys.argv = ['esptool.py'] + esptool_args
|
||||
|
||||
# Run esptool and capture output
|
||||
return_code = 0
|
||||
|
||||
# Regex to parse progress lines
|
||||
# Example: "Writing at 0x0011325c... (85 %)"
|
||||
# or "Writing at 0x0011325c [========================> ] 85.3% 622592/730050 bytes..."
|
||||
progress_pattern = re.compile(r'(\d+(?:\.\d+)?)\s*%')
|
||||
|
||||
# Save original stdout/stderr
|
||||
original_stdout = sys.stdout
|
||||
original_stderr = sys.stderr
|
||||
|
||||
try:
|
||||
# Custom output class to capture and stream esptool output
|
||||
class OutputCapture:
|
||||
def __init__(self):
|
||||
self.buffer = []
|
||||
|
||||
def write(self, text):
|
||||
if text and text.strip():
|
||||
lines = text.rstrip().split('\n')
|
||||
for line in lines:
|
||||
flash_status['output'].append(line)
|
||||
self.buffer.append(line)
|
||||
|
||||
# Try to extract progress percentage
|
||||
match = progress_pattern.search(line)
|
||||
if match:
|
||||
try:
|
||||
percent = float(match.group(1))
|
||||
flash_status['progress'] = percent
|
||||
except ValueError:
|
||||
pass
|
||||
return len(text)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
capture = OutputCapture()
|
||||
|
||||
# Redirect stdout/stderr to our capture object
|
||||
with redirect_stdout(capture), redirect_stderr(capture):
|
||||
esptool.main()
|
||||
|
||||
except SystemExit as e:
|
||||
return_code = e.code if e.code is not None else 0
|
||||
finally:
|
||||
# Restore original sys.argv and stdout/stderr
|
||||
sys.argv = original_argv
|
||||
sys.stdout = original_stdout
|
||||
sys.stderr = original_stderr
|
||||
|
||||
if return_code == 0:
|
||||
flash_status['output'].append("")
|
||||
flash_status['output'].append("✓ Flash completed successfully!")
|
||||
flash_status['success'] = True
|
||||
flash_status['progress'] = 100
|
||||
else:
|
||||
flash_status['output'].append("")
|
||||
flash_status['output'].append(f"✗ Flash failed with return code {return_code}")
|
||||
flash_status['success'] = False
|
||||
|
||||
except Exception as e:
|
||||
flash_status['output'].append(f"✗ Error: {str(e)}")
|
||||
flash_status['success'] = False
|
||||
finally:
|
||||
flash_status['running'] = False
|
||||
# Clean up temp directory
|
||||
try:
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
except:
|
||||
pass
|
||||
|
||||
def open_browser(port):
|
||||
"""Open browser to the application"""
|
||||
url = f'http://127.0.0.1:{port}'
|
||||
webbrowser.open(url)
|
||||
|
||||
def main():
|
||||
"""Run the web server"""
|
||||
import logging
|
||||
|
||||
# Disable Flask request logging to avoid colorama issues with stdout redirection
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
|
||||
port = get_free_port()
|
||||
print(f"Starting ESP32 Flasher on port {port}...")
|
||||
print(f"Open your browser to: http://127.0.0.1:{port}")
|
||||
print(f"Server will automatically shut down when browser is closed.\n")
|
||||
|
||||
# Open browser after short delay
|
||||
timer = threading.Timer(1.5, open_browser, args=[port])
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
|
||||
# Run Flask server with SocketIO
|
||||
try:
|
||||
socketio.run(app, host='127.0.0.1', port=port, debug=False, allow_unsafe_werkzeug=True, log_output=False)
|
||||
except KeyboardInterrupt:
|
||||
print('\nShutting down...')
|
||||
except SystemExit:
|
||||
print('Server stopped.')
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
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 "."
|
||||
@@ -8,5 +8,6 @@ idf_component_register(SRCS "bt_app.c" "system.c" "bubble.c" "keypad.c" "main.c"
|
||||
"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
|
||||
182
main/bt_app.c
182
main/bt_app.c
@@ -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)
|
||||
@@ -107,7 +107,7 @@ static void bt_app_a2d_heart_beat(TimerHandle_t arg);
|
||||
static void bt_app_av_sm_hdlr(uint16_t event, void *param);
|
||||
|
||||
/* utils for transfer BLuetooth Deveice Address into string form */
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size);
|
||||
static char *bda2str(const uint8_t *bda, char *str, size_t size);
|
||||
|
||||
static esp_err_t bt_try_connect_known_devices(void);
|
||||
static void bt_debug_print_known_devices(void);
|
||||
@@ -328,32 +328,14 @@ static esp_err_t bt_add_discovered_device(esp_bd_addr_t bda, const char *name)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Check if device is already known
|
||||
if (system_isDeviceKnown(bda)) {
|
||||
// Don't save discovered devices to NVS - they're not paired yet!
|
||||
// They will only be saved to NVS when successfully connected.
|
||||
// Just log that we discovered this device.
|
||||
char bda_str[18];
|
||||
ESP_LOGD(BT_AV_TAG, "Device %s (%s) already known, skipping",
|
||||
ESP_LOGI(BT_AV_TAG, "Discovered device: %s (%s)",
|
||||
name, bda2str(bda, bda_str, sizeof(bda_str)));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Create paired_device_t structure for discovered device
|
||||
paired_device_t device;
|
||||
memcpy(device.bda, bda, ESP_BD_ADDR_LEN);
|
||||
strncpy(device.name, name, DEVICE_NAME_MAX_LEN - 1);
|
||||
device.name[DEVICE_NAME_MAX_LEN - 1] = '\0';
|
||||
device.last_connected = 0; // Never connected, set to 0
|
||||
|
||||
// Save to NVS
|
||||
esp_err_t ret = system_savePairedDevice(&device);
|
||||
if (ret == ESP_OK) {
|
||||
char bda_str[18];
|
||||
ESP_LOGI(BT_AV_TAG, "Added discovered device to NVS: %s (%s)",
|
||||
device.name, bda2str(device.bda, bda_str, sizeof(bda_str)));
|
||||
} else {
|
||||
ESP_LOGE(BT_AV_TAG, "Failed to save discovered device: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t __attribute__((unused)) nvs_update_connection_timestamp(esp_bd_addr_t bda)
|
||||
@@ -485,7 +467,7 @@ static esp_err_t bt_try_next_known_device(void)
|
||||
/*********************************
|
||||
* STATIC FUNCTION DEFINITIONS
|
||||
********************************/
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
|
||||
static char *bda2str(const uint8_t *bda, char *str, size_t size)
|
||||
{
|
||||
if (bda == NULL || str == NULL || size < 18) {
|
||||
return NULL;
|
||||
@@ -559,9 +541,14 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
|
||||
}
|
||||
}
|
||||
|
||||
// Log device details for debugging
|
||||
ESP_LOGI(BT_AV_TAG, " CoD: 0x%"PRIx32", Valid: %d, RSSI: %"PRId32", EIR: %p",
|
||||
cod, esp_bt_gap_is_valid_cod(cod), rssi, eir);
|
||||
|
||||
/* search for device with MAJOR service class as "rendering" in COD */
|
||||
if (!esp_bt_gap_is_valid_cod(cod) ||
|
||||
!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) {
|
||||
ESP_LOGI(BT_AV_TAG, " Device filtered out - not an audio rendering device");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -579,16 +566,11 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
|
||||
// Add to device list for GUI
|
||||
add_device_to_list(param->disc_res.bda, (char *)s_peer_bdname, false, rssi);
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Found audio device, address %s, name %s (saved to NVS, not connecting)",
|
||||
ESP_LOGI(BT_AV_TAG, "Found audio device, address %s, name %s (added to list)",
|
||||
bda_str, s_peer_bdname);
|
||||
|
||||
// Don't automatically connect to discovered devices - just save them to NVS
|
||||
// The old code would set s_a2d_state = APP_AV_STATE_DISCOVERED and connect
|
||||
// Now we just continue discovery to find more devices
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERED;
|
||||
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
ESP_LOGI(BT_AV_TAG, "Cancel device discovery ...");
|
||||
esp_bt_gap_cancel_discovery();
|
||||
// Don't automatically connect - just continue discovering more devices
|
||||
// User will manually select a device from the menu to connect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,28 +579,34 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
|
||||
switch (event) {
|
||||
/* when device discovered a result, this event comes */
|
||||
case ESP_BT_GAP_DISC_RES_EVT: {
|
||||
if (s_a2d_state == APP_AV_STATE_DISCOVERING) {
|
||||
// Log ALL discovered devices for debugging
|
||||
char bda_str[18];
|
||||
ESP_LOGI(BT_AV_TAG, "*** Device discovered: %s (A2DP state: %d)",
|
||||
bda2str(param->disc_res.bda, bda_str, 18), s_a2d_state);
|
||||
|
||||
// Process the result regardless of A2DP state
|
||||
filter_inquiry_scan_result(param);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* when discovery state changed, this event comes */
|
||||
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
|
||||
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
|
||||
s_device_list.discovery_active = false;
|
||||
|
||||
// Notify GUI that discovery is complete so it can refresh the display
|
||||
system_notifyAll(EM_EVENT_BT_DISCOVERY_COMPLETE);
|
||||
|
||||
// Don't automatically connect - wait for user selection
|
||||
// Only connect if we're in DISCOVERED state (manually triggered by bt_connect_device)
|
||||
if (s_a2d_state == APP_AV_STATE_DISCOVERED) {
|
||||
s_a2d_state = APP_AV_STATE_CONNECTING;
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname);
|
||||
/* connect source to peer device specified by Bluetooth Device Address */
|
||||
esp_a2d_source_connect(s_peer_bda);
|
||||
} else {
|
||||
/* not discovered, continue to discover */
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
//esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
}
|
||||
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Discovery started.");
|
||||
s_device_list.discovery_active = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -717,15 +705,15 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
|
||||
// Print list of saved devices from NVS
|
||||
bt_debug_print_known_devices();
|
||||
|
||||
// Try to connect to all known devices sequentially
|
||||
esp_err_t connect_ret = bt_try_connect_all_known_devices();
|
||||
// Try to connect to known devices automatically (those we've connected to before)
|
||||
esp_err_t connect_ret = bt_try_connect_known_devices();
|
||||
if (connect_ret != ESP_OK) {
|
||||
// No known devices found, start discovery to find new devices
|
||||
ESP_LOGI(BT_AV_TAG, "No known devices found, starting discovery...");
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERING;
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
// No known devices found - stay in unconnected state
|
||||
// Don't start discovery automatically - user must do it from menu
|
||||
ESP_LOGI(BT_AV_TAG, "No previously connected devices found. User can discover devices from menu.");
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "Attempting to connect to known devices...");
|
||||
ESP_LOGI(BT_AV_TAG, "Attempting to connect to previously connected device...");
|
||||
}
|
||||
|
||||
/* create and start heart beat timer */
|
||||
@@ -810,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;
|
||||
@@ -826,8 +816,13 @@ void generate_exp(uint8_t *buf, int len, float balance)
|
||||
|
||||
click_timer -= 1.0f;
|
||||
|
||||
if (swap_lr) {
|
||||
samples[i * 2 + 0] = right;
|
||||
samples[i * 2 + 1] = left;
|
||||
} else {
|
||||
samples[i * 2 + 0] = left;
|
||||
samples[i * 2 + 1] = right;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1082,15 +1077,8 @@ static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param)
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
break;
|
||||
case BT_APP_HEART_BEAT_EVT: {
|
||||
// Try to connect to known devices, or start discovery if none available
|
||||
esp_err_t connect_ret = bt_try_connect_all_known_devices();
|
||||
|
||||
if (connect_ret != ESP_OK) {
|
||||
// No known devices, start discovery
|
||||
ESP_LOGI(BT_AV_TAG, "No known devices available, starting discovery...");
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERING;
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
}
|
||||
// Don't automatically try to reconnect - wait for user to select device
|
||||
// from the menu
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
|
||||
@@ -1118,19 +1106,33 @@ static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param)
|
||||
s_a2d_state = APP_AV_STATE_CONNECTED;
|
||||
s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
|
||||
// Check if device is already paired, if not, add it as paired
|
||||
if (!system_isDeviceKnown(s_peer_bda)) {
|
||||
ESP_LOGI(BT_AV_TAG, "Device not in paired list, adding: %s", s_peer_bdname);
|
||||
paired_device_t new_device;
|
||||
memcpy(new_device.bda, s_peer_bda, ESP_BD_ADDR_LEN);
|
||||
strncpy(new_device.name, (char*)s_peer_bdname, DEVICE_NAME_MAX_LEN - 1);
|
||||
new_device.name[DEVICE_NAME_MAX_LEN - 1] = '\0';
|
||||
new_device.last_connected = (uint32_t)(esp_timer_get_time() / 1000000);
|
||||
system_savePairedDevice(&new_device);
|
||||
|
||||
// Update the device in the GUI list to show it as paired
|
||||
for (int i = 0; i < s_device_list.count; i++) {
|
||||
if (memcmp(s_device_list.devices[i].bda, s_peer_bda, ESP_BD_ADDR_LEN) == 0) {
|
||||
s_device_list.devices[i].is_paired = true;
|
||||
ESP_LOGI(BT_AV_TAG, "Marked device as paired in GUI list: %s", s_peer_bdname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Update connection timestamp for this device
|
||||
system_updateConnectionTimestamp(s_peer_bda);
|
||||
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Connection failed, trying next device...");
|
||||
// Try next known device before giving up
|
||||
esp_err_t next_ret = bt_try_next_known_device();
|
||||
if (next_ret != ESP_OK) {
|
||||
// No more devices to try, go to unconnected state
|
||||
//s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
ESP_LOGI(BT_AV_TAG, "No known devices available, starting discovery...");
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERING;
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
}
|
||||
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Connection failed.");
|
||||
// If device was previously connected (known device), don't retry automatically
|
||||
// User can manually reconnect from menu
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1144,13 +1146,10 @@ static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param)
|
||||
* when connecting lasts more than 2 heart beat intervals.
|
||||
*/
|
||||
if (++s_connecting_intv >= 2) {
|
||||
ESP_LOGI(BT_AV_TAG, "Connection timeout, trying next device...");
|
||||
// Try next known device before giving up
|
||||
esp_err_t next_ret = bt_try_next_known_device();
|
||||
if (next_ret != ESP_OK) {
|
||||
// No more devices to try, go to unconnected state
|
||||
ESP_LOGI(BT_AV_TAG, "Connection timeout.");
|
||||
// Return to unconnected state - don't retry automatically
|
||||
// User can manually reconnect from menu
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
}
|
||||
s_connecting_intv = 0;
|
||||
}
|
||||
break;
|
||||
@@ -1468,6 +1467,7 @@ static void bt_app_task_handler(void *arg)
|
||||
if (notifiedBits & EM_EVENT_BT_REFRESH) {
|
||||
ESP_LOGI(BT_AV_TAG, "BT Refresh event received");
|
||||
bt_clear_discovered_devices();
|
||||
bt_start_discovery(); // Start new discovery after clearing
|
||||
// Notify GUI that refresh is done - could add completion event if needed
|
||||
}
|
||||
if (notifiedBits & EM_EVENT_BT_CONNECT) {
|
||||
@@ -1826,6 +1826,33 @@ void bt_clear_discovered_devices(void) {
|
||||
ESP_LOGI(BT_AV_TAG, "Cleared discovered devices, kept %d paired devices", new_count);
|
||||
}
|
||||
|
||||
void bt_clear_all_devices(void) {
|
||||
s_device_list.count = 0;
|
||||
ESP_LOGI(BT_AV_TAG, "Cleared all devices from device list");
|
||||
}
|
||||
|
||||
void bt_disconnect_current_device(void) {
|
||||
if (s_a2d_state == APP_AV_STATE_CONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Disconnecting from current device");
|
||||
|
||||
// Stop media first if playing
|
||||
if (s_media_state == APP_AV_MEDIA_STATE_STARTED) {
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_SUSPEND);
|
||||
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
|
||||
}
|
||||
|
||||
// Disconnect A2DP
|
||||
esp_a2d_source_disconnect(s_peer_bda);
|
||||
s_a2d_state = APP_AV_STATE_DISCONNECTING;
|
||||
} else if (s_a2d_state == APP_AV_STATE_CONNECTING) {
|
||||
ESP_LOGI(BT_AV_TAG, "Cancelling connection attempt");
|
||||
// Cancel connection attempt
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "No device connected (state: %d)", s_a2d_state);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_volume_up(void) {
|
||||
if (!s_volume_control_available) {
|
||||
ESP_LOGW(BT_AV_TAG, "Volume control not available");
|
||||
@@ -1848,8 +1875,11 @@ void bt_volume_down(void) {
|
||||
}
|
||||
|
||||
if (s_volume_level > 0) {
|
||||
if (s_volume_level < 10) {
|
||||
s_volume_level = 0;
|
||||
} else {
|
||||
s_volume_level -= 10; // Decrease by ~8%
|
||||
if (s_volume_level < 0) s_volume_level = 0;
|
||||
}
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Setting volume to %d", s_volume_level);
|
||||
esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, s_volume_level);
|
||||
|
||||
@@ -69,7 +69,7 @@ void bt_app_task_shut_down(void);
|
||||
void bt_app_init(void);
|
||||
|
||||
/* Bluetooth device management for GUI */
|
||||
#define MAX_BT_DEVICES 20
|
||||
#define MAX_BT_DEVICES 8 // Reduced from 20 to save memory
|
||||
#define MAX_BT_NAME_LEN 32
|
||||
|
||||
typedef struct {
|
||||
@@ -100,6 +100,12 @@ bool bt_connect_device(int device_index);
|
||||
/* Clear discovered devices (keep paired devices) */
|
||||
void bt_clear_discovered_devices(void);
|
||||
|
||||
/* Clear all devices from the device list (paired and discovered) */
|
||||
void bt_clear_all_devices(void);
|
||||
|
||||
/* Disconnect from currently connected device */
|
||||
void bt_disconnect_current_device(void);
|
||||
|
||||
/* Volume control functions */
|
||||
void bt_volume_up(void);
|
||||
void bt_volume_down(void);
|
||||
|
||||
10
main/gpio.h
10
main/gpio.h
@@ -4,9 +4,15 @@
|
||||
// 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_LED_0 32
|
||||
#define PIN_NUM_LED_1 33
|
||||
#define PIN_NUM_LED_2 25
|
||||
#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
|
||||
|
||||
1093
main/gui.c
1093
main/gui.c
File diff suppressed because it is too large
Load Diff
47
main/main.c
47
main/main.c
@@ -28,6 +28,7 @@
|
||||
#include "gpio.h"
|
||||
#include "keypad.h"
|
||||
#include "system.h"
|
||||
#include "battery.h"
|
||||
|
||||
|
||||
|
||||
@@ -104,20 +105,47 @@ static inline float LPF_Update(LowPassFilter *f, float input) {
|
||||
* STATIC FUNCTION DEFINITIONS
|
||||
********************************/
|
||||
|
||||
// Helper function to set LED with verification
|
||||
static void set_led_level(gpio_num_t pin, uint32_t level)
|
||||
{
|
||||
esp_err_t err = gpio_set_level(pin, level);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set GPIO %d to %lu: %s", pin, level, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
static void init_gpio(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
|
||||
// Reset GPIO peripheral to clear any SPI/peripheral configurations
|
||||
gpio_reset_pin(PIN_NUM_LED_0);
|
||||
gpio_reset_pin(PIN_NUM_LED_1);
|
||||
gpio_reset_pin(PIN_NUM_LED_2);
|
||||
|
||||
// Configure output GPIO
|
||||
io_conf.pin_bit_mask = (1ULL << PIN_NUM_LED_1) | (1ULL << PIN_NUM_LED_2);
|
||||
io_conf.pin_bit_mask = (1ULL << PIN_NUM_LED_0) | (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);
|
||||
|
||||
// Set initial state
|
||||
gpio_set_level(PIN_NUM_LED_0, 0);
|
||||
gpio_set_level(PIN_NUM_LED_1, 0);
|
||||
gpio_set_level(PIN_NUM_LED_2, 0);
|
||||
|
||||
ESP_LOGI(TAG, "LED pins configured: %d, %d, %d", PIN_NUM_LED_0, PIN_NUM_LED_1, PIN_NUM_LED_2);
|
||||
|
||||
|
||||
// Configure charge status input GPIO
|
||||
io_conf.pin_bit_mask = (1ULL << PIN_NUM_CHARGE_STATUS);
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
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
|
||||
@@ -275,21 +303,32 @@ void app_main(void)
|
||||
print_heap_info("POST_SYSTEM");
|
||||
|
||||
// Initialize IMU
|
||||
ESP_ERROR_CHECK(lsm6dsv_init(22, 21)); // SCL = IO14, SDA = IO15
|
||||
ESP_ERROR_CHECK(lsm6dsv_init(22, 21)); // SCL = IO14, SDA = IO15`
|
||||
print_heap_info("POST_IMU");
|
||||
|
||||
// Create IMU task
|
||||
TaskHandle_t h = xTaskCreate(imu_task, "imu_task", 2048, NULL, 5, NULL);
|
||||
TaskHandle_t h = NULL;
|
||||
xTaskCreate(imu_task, "imu_task", 2048, NULL, 5, &h);
|
||||
print_heap_info("POST_IMU_TASK");
|
||||
|
||||
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);
|
||||
|
||||
gui_start();
|
||||
print_heap_info("POST_GUI");
|
||||
gpio_set_level(PIN_NUM_LED_2, 1);
|
||||
|
||||
|
||||
battery_start_monitoring_task();
|
||||
#if 0
|
||||
|
||||
keypad_start();
|
||||
@@ -325,6 +364,8 @@ void app_main(void)
|
||||
#else
|
||||
while (1)
|
||||
{
|
||||
|
||||
|
||||
system_processNvsRequests();
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
263
main/system.c
263
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);
|
||||
@@ -22,6 +26,11 @@ void system_init(void)
|
||||
_systemState.zeroAngle = 0.0f;
|
||||
_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();
|
||||
|
||||
@@ -29,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)
|
||||
@@ -57,6 +72,82 @@ float system_getAngle(void)
|
||||
return angle;
|
||||
}
|
||||
|
||||
void system_setChargeStatus(bool charging)
|
||||
{
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
_systemState.isCharging = charging;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
}
|
||||
|
||||
bool system_getChargeStatus(void)
|
||||
{
|
||||
bool charging;
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
charging = _systemState.isCharging;
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
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);
|
||||
@@ -214,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) {
|
||||
@@ -356,9 +587,9 @@ void system_processNvsRequests(void) {
|
||||
}
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
|
||||
request.response_ready = true;
|
||||
// Send the result directly as the notification value (not a pointer)
|
||||
if (request.requestor) {
|
||||
xTaskNotify(request.requestor, (uint32_t)&request, eSetValueWithOverwrite);
|
||||
xTaskNotify(request.requestor, (uint32_t)request.result, eSetValueWithOverwrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,8 +627,8 @@ esp_err_t system_savePairedDevice(const paired_device_t *device) {
|
||||
if (xQueueSend(_nvsRequestQueue, &request, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
|
||||
uint32_t notification;
|
||||
if (xTaskNotifyWait(0, UINT32_MAX, ¬ification, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
|
||||
nvs_request_t *response = (nvs_request_t*)notification;
|
||||
return response->result;
|
||||
// notification contains the result directly (not a pointer)
|
||||
return (esp_err_t)notification;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
@@ -425,8 +656,8 @@ esp_err_t system_removePairedDevice(esp_bd_addr_t bda) {
|
||||
if (xQueueSend(_nvsRequestQueue, &request, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
|
||||
uint32_t notification;
|
||||
if (xTaskNotifyWait(0, UINT32_MAX, ¬ification, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
|
||||
nvs_request_t *response = (nvs_request_t*)notification;
|
||||
return response->result;
|
||||
// notification contains the result directly (not a pointer)
|
||||
return (esp_err_t)notification;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
@@ -464,8 +695,8 @@ esp_err_t system_updateConnectionTimestamp(esp_bd_addr_t bda) {
|
||||
if (xQueueSend(_nvsRequestQueue, &request, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
|
||||
uint32_t notification;
|
||||
if (xTaskNotifyWait(0, UINT32_MAX, ¬ification, pdMS_TO_TICKS(NVS_TIMEOUT_MS)) == pdTRUE) {
|
||||
nvs_request_t *response = (nvs_request_t*)notification;
|
||||
return response->result;
|
||||
// notification contains the result directly (not a pointer)
|
||||
return (esp_err_t)notification;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
@@ -480,3 +711,19 @@ const paired_device_t* system_getPairedDevices(size_t *count) {
|
||||
|
||||
return (const paired_device_t*)_systemState.pairedDevices;
|
||||
}
|
||||
|
||||
esp_err_t system_clearAllPairedDevices(void) {
|
||||
// Directly clear in-memory state and save to NVS
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
_systemState.pairedDeviceCount = 0;
|
||||
esp_err_t ret = nvs_save_devices_internal(_systemState.pairedDevices, 0);
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI("system", "Cleared all paired devices from NVS");
|
||||
} else {
|
||||
ESP_LOGE("system", "Failed to clear paired devices: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -52,6 +52,19 @@ typedef struct SystemState_s
|
||||
paired_device_t pairedDevices[MAX_PAIRED_DEVICES];
|
||||
size_t pairedDeviceCount;
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
@@ -67,6 +80,7 @@ typedef struct SystemState_s
|
||||
#define EM_EVENT_BT_CONNECT (1UL<<3)
|
||||
#define EM_EVENT_VOLUME_UP (1UL<<4)
|
||||
#define EM_EVENT_VOLUME_DOWN (1UL<<5)
|
||||
#define EM_EVENT_BT_DISCOVERY_COMPLETE (1UL<<6)
|
||||
// …add more event bit-masks here…
|
||||
|
||||
typedef struct {
|
||||
@@ -83,6 +97,20 @@ ImuData_t system_getImuData(void);
|
||||
int system_getPrimaryAxis(void);
|
||||
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);
|
||||
@@ -104,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 {
|
||||
@@ -133,6 +165,7 @@ bool system_isDeviceKnown(esp_bd_addr_t bda);
|
||||
esp_err_t system_getKnownDeviceCount(size_t *count);
|
||||
esp_err_t system_updateConnectionTimestamp(esp_bd_addr_t bda);
|
||||
const paired_device_t* system_getPairedDevices(size_t *count);
|
||||
esp_err_t system_clearAllPairedDevices(void);
|
||||
|
||||
|
||||
#define NVS_TIMEOUT_MS 5000
|
||||
|
||||
196
sdkconfig
196
sdkconfig
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Automatically generated file. DO NOT EDIT.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.3.1 Project Configuration
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Configuration
|
||||
#
|
||||
CONFIG_SOC_BROWNOUT_RESET_SUPPORTED="Not determined"
|
||||
CONFIG_SOC_TWAI_BRP_DIV_SUPPORTED="Not determined"
|
||||
@@ -91,12 +91,14 @@ CONFIG_SOC_GPIO_OUT_RANGE_MAX=33
|
||||
CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0xEF0FEA
|
||||
CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y
|
||||
CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3
|
||||
CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y
|
||||
CONFIG_SOC_I2C_NUM=2
|
||||
CONFIG_SOC_HP_I2C_NUM=2
|
||||
CONFIG_SOC_I2C_FIFO_LEN=32
|
||||
CONFIG_SOC_I2C_CMD_REG_NUM=16
|
||||
CONFIG_SOC_I2C_SUPPORT_SLAVE=y
|
||||
CONFIG_SOC_I2C_SUPPORT_APB=y
|
||||
CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y
|
||||
CONFIG_SOC_I2C_STOP_INDEPENDENT=y
|
||||
CONFIG_SOC_I2S_NUM=2
|
||||
CONFIG_SOC_I2S_HW_VERSION_1=y
|
||||
@@ -111,6 +113,7 @@ CONFIG_SOC_I2S_SUPPORTS_ADC_DAC=y
|
||||
CONFIG_SOC_I2S_SUPPORTS_ADC=y
|
||||
CONFIG_SOC_I2S_SUPPORTS_DAC=y
|
||||
CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA=y
|
||||
CONFIG_SOC_I2S_MAX_DATA_WIDTH=24
|
||||
CONFIG_SOC_I2S_TRANS_SIZE_ALIGN_WORD=y
|
||||
CONFIG_SOC_I2S_LCD_I80_VARIANT=y
|
||||
CONFIG_SOC_LCD_I80_SUPPORTED=y
|
||||
@@ -120,6 +123,7 @@ CONFIG_SOC_LEDC_HAS_TIMER_SPECIFIC_MUX=y
|
||||
CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y
|
||||
CONFIG_SOC_LEDC_SUPPORT_REF_TICK=y
|
||||
CONFIG_SOC_LEDC_SUPPORT_HS_MODE=y
|
||||
CONFIG_SOC_LEDC_TIMER_NUM=4
|
||||
CONFIG_SOC_LEDC_CHANNEL_NUM=8
|
||||
CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20
|
||||
CONFIG_SOC_MCPWM_GROUPS=2
|
||||
@@ -172,6 +176,8 @@ CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2
|
||||
CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=64
|
||||
CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4
|
||||
CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y
|
||||
CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32
|
||||
CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16
|
||||
CONFIG_SOC_TOUCH_SENSOR_VERSION=1
|
||||
CONFIG_SOC_TOUCH_SENSOR_NUM=10
|
||||
CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1
|
||||
@@ -214,6 +220,7 @@ CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y
|
||||
CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y
|
||||
CONFIG_SOC_PM_SUPPORT_MODEM_PD=y
|
||||
CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y
|
||||
CONFIG_SOC_PM_MODEM_PD_BY_SW=y
|
||||
CONFIG_SOC_CLK_APLL_SUPPORTED=y
|
||||
CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y
|
||||
CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y
|
||||
@@ -236,10 +243,11 @@ CONFIG_SOC_PHY_COMBO_MODULE=y
|
||||
CONFIG_SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=y
|
||||
CONFIG_IDF_CMAKE=y
|
||||
CONFIG_IDF_TOOLCHAIN="gcc"
|
||||
CONFIG_IDF_TOOLCHAIN_GCC=y
|
||||
CONFIG_IDF_TARGET_ARCH_XTENSA=y
|
||||
CONFIG_IDF_TARGET_ARCH="xtensa"
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_IDF_INIT_VERSION="5.3.1"
|
||||
CONFIG_IDF_INIT_VERSION="5.4.1"
|
||||
CONFIG_IDF_TARGET_ESP32=y
|
||||
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000
|
||||
|
||||
@@ -273,6 +281,10 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set
|
||||
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set
|
||||
# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set
|
||||
|
||||
#
|
||||
# Log
|
||||
#
|
||||
# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set
|
||||
# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set
|
||||
# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set
|
||||
@@ -281,6 +293,14 @@ CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y
|
||||
# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL=3
|
||||
|
||||
#
|
||||
# Format
|
||||
#
|
||||
# CONFIG_BOOTLOADER_LOG_COLORS is not set
|
||||
CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y
|
||||
# end of Format
|
||||
# end of Log
|
||||
|
||||
#
|
||||
# Serial Flash Configurations
|
||||
#
|
||||
@@ -336,6 +356,7 @@ CONFIG_ESP_ROM_HAS_SW_FLOAT=y
|
||||
CONFIG_ESP_ROM_USB_OTG_NUM=-1
|
||||
CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=-1
|
||||
CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y
|
||||
CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y
|
||||
|
||||
#
|
||||
# Serial flasher config
|
||||
@@ -377,6 +398,7 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
|
||||
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
|
||||
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
|
||||
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
|
||||
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
@@ -400,6 +422,7 @@ CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
|
||||
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
|
||||
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
|
||||
# CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE is not set
|
||||
CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
|
||||
# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
|
||||
@@ -410,14 +433,18 @@ CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
|
||||
# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
|
||||
# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set
|
||||
# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set
|
||||
# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set
|
||||
# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set
|
||||
# CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS is not set
|
||||
# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set
|
||||
# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set
|
||||
# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set
|
||||
# CONFIG_COMPILER_DUMP_RTL_FILES is not set
|
||||
CONFIG_COMPILER_RT_LIB_GCCLIB=y
|
||||
CONFIG_COMPILER_RT_LIB_NAME="gcc"
|
||||
# CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING is not set
|
||||
CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE=y
|
||||
# CONFIG_COMPILER_STATIC_ANALYZER is not set
|
||||
# end of Compiler options
|
||||
|
||||
#
|
||||
@@ -461,10 +488,20 @@ CONFIG_BT_ENC_KEY_SIZE_CTRL_VSC=y
|
||||
# CONFIG_BT_ENC_KEY_SIZE_CTRL_NONE is not set
|
||||
# CONFIG_BT_CLASSIC_BQB_ENABLED is not set
|
||||
CONFIG_BT_A2DP_ENABLE=y
|
||||
CONFIG_BT_AVRCP_ENABLED=y
|
||||
|
||||
#
|
||||
# AVRCP Features
|
||||
#
|
||||
CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=y
|
||||
# end of AVRCP Features
|
||||
|
||||
# CONFIG_BT_SPP_ENABLED is not set
|
||||
# CONFIG_BT_L2CAP_ENABLED is not set
|
||||
# CONFIG_BT_SDP_COMMON_ENABLED is not set
|
||||
# CONFIG_BT_HFP_ENABLE is not set
|
||||
# CONFIG_BT_HID_ENABLED is not set
|
||||
CONFIG_BT_GOEPC_ENABLED=y
|
||||
CONFIG_BT_BLE_ENABLED=y
|
||||
CONFIG_BT_GATTS_ENABLE=y
|
||||
# CONFIG_BT_GATTS_PPCP_CHAR_GAP is not set
|
||||
@@ -485,6 +522,7 @@ CONFIG_BT_GATTC_CONNECT_RETRY_COUNT=3
|
||||
CONFIG_BT_BLE_SMP_ENABLE=y
|
||||
# CONFIG_BT_SMP_SLAVE_CON_PARAMS_UPD_ENABLE is not set
|
||||
# CONFIG_BT_BLE_SMP_ID_RESET_ENABLE is not set
|
||||
CONFIG_BT_BLE_SMP_BOND_NVS_FLASH=y
|
||||
# CONFIG_BT_STACK_NO_LOG is not set
|
||||
|
||||
#
|
||||
@@ -672,8 +710,8 @@ CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT=30
|
||||
CONFIG_BT_MAX_DEVICE_NAME_LEN=32
|
||||
# CONFIG_BT_BLE_RPA_SUPPORTED is not set
|
||||
CONFIG_BT_BLE_RPA_TIMEOUT=900
|
||||
# CONFIG_BT_BLE_42_FEATURES_SUPPORTED is not set
|
||||
# CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL is not set
|
||||
# CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS is not set
|
||||
# end of Bluedroid Options
|
||||
|
||||
#
|
||||
@@ -682,6 +720,7 @@ CONFIG_BT_BLE_RPA_TIMEOUT=900
|
||||
# CONFIG_BTDM_CTRL_MODE_BLE_ONLY is not set
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
# CONFIG_BTDM_CTRL_MODE_BTDM is not set
|
||||
CONFIG_BTDM_CTRL_BR_EDR_MIN_ENC_KEY_SZ_DFT=7
|
||||
CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN=2
|
||||
CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN=0
|
||||
# CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI is not set
|
||||
@@ -692,11 +731,16 @@ CONFIG_BTDM_CTRL_PCM_ROLE_MASTER=y
|
||||
# CONFIG_BTDM_CTRL_PCM_ROLE_SLAVE is not set
|
||||
CONFIG_BTDM_CTRL_PCM_POLAR_FALLING_EDGE=y
|
||||
# CONFIG_BTDM_CTRL_PCM_POLAR_RISING_EDGE is not set
|
||||
CONFIG_BTDM_CTRL_PCM_FSYNCSHP_STEREO_MODE=y
|
||||
# CONFIG_BTDM_CTRL_PCM_FSYNCSHP_MONO_MODE_LF is not set
|
||||
# CONFIG_BTDM_CTRL_PCM_FSYNCSHP_MONO_MODE_FF is not set
|
||||
CONFIG_BTDM_CTRL_PCM_ROLE_EFF=0
|
||||
CONFIG_BTDM_CTRL_PCM_POLAR_EFF=0
|
||||
CONFIG_BTDM_CTRL_PCM_FSYNCSHP_EFF=0
|
||||
CONFIG_BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT=y
|
||||
CONFIG_BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT_EFF=y
|
||||
CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF=0
|
||||
CONFIG_BTDM_CTRL_BR_EDR_MIN_ENC_KEY_SZ_DFT_EFF=7
|
||||
CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF=2
|
||||
CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF=0
|
||||
CONFIG_BTDM_CTRL_PINNED_TO_CORE_0=y
|
||||
@@ -716,6 +760,14 @@ CONFIG_BTDM_CTRL_LPCLK_SEL_MAIN_XTAL=y
|
||||
|
||||
CONFIG_BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF=1
|
||||
# CONFIG_BTDM_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set
|
||||
# CONFIG_BTDM_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set
|
||||
|
||||
#
|
||||
# BLE disconnects when Instant Passed (0x28) occurs
|
||||
#
|
||||
# end of BLE disconnects when Instant Passed (0x28) occurs
|
||||
|
||||
# CONFIG_BTDM_CTRL_CONTROLLER_DEBUG_MODE_1 is not set
|
||||
CONFIG_BTDM_RESERVE_DRAM=0xdb5c
|
||||
CONFIG_BTDM_CTRL_HLI=y
|
||||
# end of Controller Options
|
||||
@@ -724,6 +776,7 @@ CONFIG_BTDM_CTRL_HLI=y
|
||||
# Common Options
|
||||
#
|
||||
CONFIG_BT_ALARM_MAX_NUM=50
|
||||
# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set
|
||||
# end of Common Options
|
||||
|
||||
# CONFIG_BT_HCI_LOG_DEBUG_EN is not set
|
||||
@@ -757,6 +810,7 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y
|
||||
#
|
||||
CONFIG_ADC_DISABLE_DAC=y
|
||||
# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
|
||||
#
|
||||
# Legacy ADC Calibration Configuration
|
||||
@@ -772,42 +826,49 @@ CONFIG_ADC_CAL_LUT_ENABLE=y
|
||||
# Legacy DAC Driver Configurations
|
||||
#
|
||||
# CONFIG_DAC_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_DAC_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
# end of Legacy DAC Driver Configurations
|
||||
|
||||
#
|
||||
# Legacy MCPWM Driver Configurations
|
||||
#
|
||||
# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
# end of Legacy MCPWM Driver Configurations
|
||||
|
||||
#
|
||||
# Legacy Timer Group Driver Configurations
|
||||
#
|
||||
# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
# end of Legacy Timer Group Driver Configurations
|
||||
|
||||
#
|
||||
# Legacy RMT Driver Configurations
|
||||
#
|
||||
# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
# end of Legacy RMT Driver Configurations
|
||||
|
||||
#
|
||||
# Legacy I2S Driver Configurations
|
||||
#
|
||||
# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
# end of Legacy I2S Driver Configurations
|
||||
|
||||
#
|
||||
# Legacy PCNT Driver Configurations
|
||||
#
|
||||
# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
# end of Legacy PCNT Driver Configurations
|
||||
|
||||
#
|
||||
# Legacy SDM Driver Configurations
|
||||
#
|
||||
# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set
|
||||
# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set
|
||||
# end of Legacy SDM Driver Configurations
|
||||
# end of Driver Configurations
|
||||
|
||||
@@ -859,6 +920,7 @@ CONFIG_ADC_DISABLE_DAC_OUTPUT=y
|
||||
CONFIG_ESP_COEX_ENABLED=y
|
||||
CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
|
||||
# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set
|
||||
# CONFIG_ESP_COEX_GPIO_DEBUG is not set
|
||||
# end of Wireless Coexistence
|
||||
|
||||
#
|
||||
@@ -897,6 +959,7 @@ CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y
|
||||
#
|
||||
# CONFIG_I2C_ISR_IRAM_SAFE is not set
|
||||
# CONFIG_I2C_ENABLE_DEBUG_LOG is not set
|
||||
# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set
|
||||
# end of ESP-Driver:I2C Configurations
|
||||
|
||||
#
|
||||
@@ -1004,6 +1067,13 @@ CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y
|
||||
CONFIG_ESP_GDBSTUB_MAX_TASKS=32
|
||||
# end of GDB Stub
|
||||
|
||||
#
|
||||
# ESP HID
|
||||
#
|
||||
CONFIG_ESPHID_TASK_SIZE_BT=2048
|
||||
CONFIG_ESPHID_TASK_SIZE_BLE=4096
|
||||
# end of ESP HID
|
||||
|
||||
#
|
||||
# ESP HTTP client
|
||||
#
|
||||
@@ -1011,6 +1081,7 @@ CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
|
||||
# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
|
||||
# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set
|
||||
# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set
|
||||
CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000
|
||||
# end of ESP HTTP client
|
||||
|
||||
#
|
||||
@@ -1023,6 +1094,7 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32
|
||||
# CONFIG_HTTPD_LOG_PURGE_DATA is not set
|
||||
# CONFIG_HTTPD_WS_SUPPORT is not set
|
||||
# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set
|
||||
CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000
|
||||
# end of HTTP Server
|
||||
|
||||
#
|
||||
@@ -1030,12 +1102,14 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32
|
||||
#
|
||||
# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set
|
||||
# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set
|
||||
CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000
|
||||
# end of ESP HTTPS OTA
|
||||
|
||||
#
|
||||
# ESP HTTPS server
|
||||
#
|
||||
# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set
|
||||
CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000
|
||||
# end of ESP HTTPS server
|
||||
|
||||
#
|
||||
@@ -1060,6 +1134,12 @@ CONFIG_ESP_REV_MIN_FULL=0
|
||||
#
|
||||
CONFIG_ESP32_REV_MAX_FULL=399
|
||||
CONFIG_ESP_REV_MAX_FULL=399
|
||||
CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0
|
||||
CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99
|
||||
|
||||
#
|
||||
# Maximum Supported ESP32 eFuse Block Revision (eFuse Block Rev v0.99)
|
||||
#
|
||||
# end of Chip revision
|
||||
|
||||
#
|
||||
@@ -1112,6 +1192,7 @@ CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y
|
||||
# Main XTAL Config
|
||||
#
|
||||
# CONFIG_XTAL_FREQ_26 is not set
|
||||
# CONFIG_XTAL_FREQ_32 is not set
|
||||
CONFIG_XTAL_FREQ_40=y
|
||||
# CONFIG_XTAL_FREQ_AUTO is not set
|
||||
CONFIG_XTAL_FREQ=40
|
||||
@@ -1121,31 +1202,29 @@ CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y
|
||||
# end of Hardware Settings
|
||||
|
||||
#
|
||||
# LCD and Touch Panel
|
||||
# ESP-Driver:LCD Controller Configurations
|
||||
#
|
||||
|
||||
#
|
||||
# LCD Touch Drivers are maintained in the IDF Component Registry
|
||||
#
|
||||
|
||||
#
|
||||
# LCD Peripheral Configuration
|
||||
#
|
||||
CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE=32
|
||||
# CONFIG_LCD_ENABLE_DEBUG_LOG is not set
|
||||
# end of LCD Peripheral Configuration
|
||||
# end of LCD and Touch Panel
|
||||
# end of ESP-Driver:LCD Controller Configurations
|
||||
|
||||
#
|
||||
# ESP-MM: Memory Management Configurations
|
||||
#
|
||||
# end of ESP-MM: Memory Management Configurations
|
||||
|
||||
#
|
||||
# ESP NETIF Adapter
|
||||
#
|
||||
CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120
|
||||
# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set
|
||||
CONFIG_ESP_NETIF_TCPIP_LWIP=y
|
||||
# CONFIG_ESP_NETIF_LOOPBACK is not set
|
||||
CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
|
||||
CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y
|
||||
# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set
|
||||
# CONFIG_ESP_NETIF_L2_TAP is not set
|
||||
# CONFIG_ESP_NETIF_BRIDGE_EN is not set
|
||||
# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
|
||||
# end of ESP NETIF Adapter
|
||||
|
||||
#
|
||||
@@ -1162,17 +1241,20 @@ CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y
|
||||
CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20
|
||||
CONFIG_ESP_PHY_MAX_TX_POWER=20
|
||||
# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set
|
||||
# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set
|
||||
CONFIG_ESP_PHY_RF_CAL_PARTIAL=y
|
||||
# CONFIG_ESP_PHY_RF_CAL_NONE is not set
|
||||
# CONFIG_ESP_PHY_RF_CAL_FULL is not set
|
||||
CONFIG_ESP_PHY_CALIBRATION_MODE=0
|
||||
# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set
|
||||
# CONFIG_ESP_PHY_RECORD_USED_TIME is not set
|
||||
# end of PHY
|
||||
|
||||
#
|
||||
# Power Management
|
||||
#
|
||||
# CONFIG_PM_ENABLE is not set
|
||||
CONFIG_PM_SLP_IRAM_OPT=y
|
||||
# end of Power Management
|
||||
|
||||
#
|
||||
@@ -1187,6 +1269,11 @@ CONFIG_ESP_PHY_CALIBRATION_MODE=0
|
||||
# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set
|
||||
# end of ESP Ringbuf
|
||||
|
||||
#
|
||||
# ESP Security Specific
|
||||
#
|
||||
# end of ESP Security Specific
|
||||
|
||||
#
|
||||
# ESP System Settings
|
||||
#
|
||||
@@ -1404,6 +1491,9 @@ CONFIG_FATFS_FS_LOCK=0
|
||||
CONFIG_FATFS_TIMEOUT_MS=10000
|
||||
CONFIG_FATFS_PER_FILE_CACHE=y
|
||||
# CONFIG_FATFS_USE_FASTSEEK is not set
|
||||
CONFIG_FATFS_USE_STRFUNC_NONE=y
|
||||
# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set
|
||||
# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0
|
||||
# CONFIG_FATFS_IMMEDIATE_FSYNC is not set
|
||||
# CONFIG_FATFS_USE_LABEL is not set
|
||||
@@ -1429,6 +1519,7 @@ CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536
|
||||
# CONFIG_FREERTOS_USE_TICK_HOOK is not set
|
||||
CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
|
||||
# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set
|
||||
CONFIG_FREERTOS_USE_TIMERS=y
|
||||
CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc"
|
||||
# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set
|
||||
# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set
|
||||
@@ -1464,6 +1555,11 @@ CONFIG_FREERTOS_SYSTICK_USES_CCOUNT=y
|
||||
# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
|
||||
# end of Port
|
||||
|
||||
#
|
||||
# Extra
|
||||
#
|
||||
# end of Extra
|
||||
|
||||
CONFIG_FREERTOS_PORT=y
|
||||
CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
|
||||
CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
|
||||
@@ -1501,7 +1597,11 @@ CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS=y
|
||||
# end of Heap memory debugging
|
||||
|
||||
#
|
||||
# Log output
|
||||
# Log
|
||||
#
|
||||
|
||||
#
|
||||
# Log Level
|
||||
#
|
||||
# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set
|
||||
# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set
|
||||
@@ -1514,11 +1614,29 @@ CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y
|
||||
# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set
|
||||
# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set
|
||||
CONFIG_LOG_MAXIMUM_LEVEL=3
|
||||
|
||||
#
|
||||
# Level Settings
|
||||
#
|
||||
# CONFIG_LOG_MASTER_LEVEL is not set
|
||||
CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y
|
||||
# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set
|
||||
# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set
|
||||
CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y
|
||||
# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set
|
||||
CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y
|
||||
CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31
|
||||
# end of Level Settings
|
||||
# end of Log Level
|
||||
|
||||
#
|
||||
# Format
|
||||
#
|
||||
CONFIG_LOG_COLORS=y
|
||||
CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y
|
||||
# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set
|
||||
# end of Log output
|
||||
# end of Format
|
||||
# end of Log
|
||||
|
||||
#
|
||||
# LWIP
|
||||
@@ -1557,6 +1675,8 @@ CONFIG_LWIP_ESP_MLDV6_REPORT=y
|
||||
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
|
||||
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
|
||||
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
|
||||
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
|
||||
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
|
||||
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
|
||||
# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set
|
||||
@@ -1571,6 +1691,7 @@ CONFIG_LWIP_DHCPS=y
|
||||
CONFIG_LWIP_DHCPS_LEASE_UNIT=60
|
||||
CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8
|
||||
CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
|
||||
CONFIG_LWIP_DHCPS_ADD_DNS=y
|
||||
# end of DHCP server
|
||||
|
||||
# CONFIG_LWIP_AUTOIP is not set
|
||||
@@ -1629,9 +1750,12 @@ CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
|
||||
# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set
|
||||
# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set
|
||||
CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF
|
||||
# CONFIG_LWIP_PPP_SUPPORT is not set
|
||||
CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3
|
||||
CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5
|
||||
CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5
|
||||
CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3
|
||||
CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10
|
||||
# CONFIG_LWIP_PPP_SUPPORT is not set
|
||||
# CONFIG_LWIP_SLIP_SUPPORT is not set
|
||||
|
||||
#
|
||||
@@ -1661,8 +1785,10 @@ CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000
|
||||
#
|
||||
# DNS
|
||||
#
|
||||
CONFIG_LWIP_DNS_MAX_HOST_IP=1
|
||||
CONFIG_LWIP_DNS_MAX_SERVERS=3
|
||||
# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set
|
||||
# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set
|
||||
# end of DNS
|
||||
|
||||
CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7
|
||||
@@ -1686,6 +1812,8 @@ CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y
|
||||
CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y
|
||||
# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set
|
||||
# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set
|
||||
CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y
|
||||
# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set
|
||||
CONFIG_LWIP_HOOK_IP6_INPUT_NONE=y
|
||||
# CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT is not set
|
||||
# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set
|
||||
@@ -1744,6 +1872,7 @@ CONFIG_MBEDTLS_HAVE_TIME=y
|
||||
# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set
|
||||
CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y
|
||||
CONFIG_MBEDTLS_SHA512_C=y
|
||||
CONFIG_MBEDTLS_SHA3_C=y
|
||||
CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y
|
||||
# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set
|
||||
# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set
|
||||
@@ -1797,6 +1926,8 @@ CONFIG_MBEDTLS_X509_CSR_PARSE_C=y
|
||||
# end of Certificates
|
||||
|
||||
CONFIG_MBEDTLS_ECP_C=y
|
||||
CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y
|
||||
CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y
|
||||
# CONFIG_MBEDTLS_DHM_C is not set
|
||||
CONFIG_MBEDTLS_ECDH_C=y
|
||||
CONFIG_MBEDTLS_ECDSA_C=y
|
||||
@@ -1820,6 +1951,7 @@ CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y
|
||||
# CONFIG_MBEDTLS_HKDF_C is not set
|
||||
# CONFIG_MBEDTLS_THREADING_C is not set
|
||||
CONFIG_MBEDTLS_ERROR_STRINGS=y
|
||||
CONFIG_MBEDTLS_FS_IO=y
|
||||
# end of mbedTLS
|
||||
|
||||
#
|
||||
@@ -1867,25 +1999,10 @@ CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y
|
||||
# CONFIG_OPENTHREAD_ENABLED is not set
|
||||
|
||||
#
|
||||
# Thread Operational Dataset
|
||||
# OpenThread Spinel
|
||||
#
|
||||
CONFIG_OPENTHREAD_NETWORK_NAME="OpenThread-ESP"
|
||||
CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX="fd00:db8:a0:0::/64"
|
||||
CONFIG_OPENTHREAD_NETWORK_CHANNEL=15
|
||||
CONFIG_OPENTHREAD_NETWORK_PANID=0x1234
|
||||
CONFIG_OPENTHREAD_NETWORK_EXTPANID="dead00beef00cafe"
|
||||
CONFIG_OPENTHREAD_NETWORK_MASTERKEY="00112233445566778899aabbccddeeff"
|
||||
CONFIG_OPENTHREAD_NETWORK_PSKC="104810e2315100afd6bc9215a6bfac53"
|
||||
# end of Thread Operational Dataset
|
||||
|
||||
CONFIG_OPENTHREAD_XTAL_ACCURACY=130
|
||||
# CONFIG_OPENTHREAD_SPINEL_ONLY is not set
|
||||
# CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE is not set
|
||||
|
||||
#
|
||||
# Thread Address Query Config
|
||||
#
|
||||
# end of Thread Address Query Config
|
||||
# end of OpenThread Spinel
|
||||
# end of OpenThread
|
||||
|
||||
#
|
||||
@@ -1894,6 +2011,7 @@ CONFIG_OPENTHREAD_XTAL_ACCURACY=130
|
||||
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y
|
||||
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y
|
||||
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
|
||||
CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y
|
||||
# end of Protocomm
|
||||
|
||||
#
|
||||
@@ -1936,6 +2054,7 @@ CONFIG_SPI_FLASH_BROWNOUT_RESET=y
|
||||
# Features here require specific hardware (READ DOCS FIRST!)
|
||||
#
|
||||
CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50
|
||||
# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set
|
||||
# end of Optional and Experimental Features (READ DOCS FIRST)
|
||||
# end of Main Flash configuration
|
||||
|
||||
@@ -2065,6 +2184,8 @@ CONFIG_VFS_MAX_COUNT=8
|
||||
#
|
||||
CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1
|
||||
# end of Host File System I/O (Semihosting)
|
||||
|
||||
CONFIG_VFS_INITIALIZE_DEV_NULL=y
|
||||
# end of Virtual file system
|
||||
|
||||
#
|
||||
@@ -2082,6 +2203,7 @@ CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16
|
||||
CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
|
||||
# CONFIG_WIFI_PROV_BLE_BONDING is not set
|
||||
# CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set
|
||||
# CONFIG_WIFI_PROV_BLE_NOTIFY is not set
|
||||
# CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV is not set
|
||||
CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
|
||||
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
|
||||
@@ -2247,7 +2369,7 @@ CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
|
||||
# Enable built-in fonts
|
||||
#
|
||||
CONFIG_LV_FONT_MONTSERRAT_8=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_10 is not set
|
||||
CONFIG_LV_FONT_MONTSERRAT_10=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_12=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_14=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_16 is not set
|
||||
@@ -2811,8 +2933,6 @@ CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
|
||||
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
|
||||
CONFIG_ESP32_WIFI_TX_BA_WIN=6
|
||||
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
|
||||
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
|
||||
CONFIG_ESP32_WIFI_RX_BA_WIN=6
|
||||
CONFIG_ESP32_WIFI_RX_BA_WIN=6
|
||||
CONFIG_ESP32_WIFI_NVS_ENABLED=y
|
||||
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y
|
||||
|
||||
3008
sdkconfig.old
Normal file
3008
sdkconfig.old
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user