From 4651d4c88a048aebec74e3dfd750787f50a45f0b Mon Sep 17 00:00:00 2001 From: Brent Perteet Date: Wed, 1 Apr 2026 20:34:59 -0500 Subject: [PATCH] Initial project setup: ESP-IDF + C++ + FreeRTOS + WiFi + web server --- .clangd | 2 + .devcontainer/Dockerfile | 13 +++ .devcontainer/devcontainer.json | 19 ++++ .gitignore | 78 +++++++++++++++ .vscode/ESP_SETUP.md | 66 +++++++++++++ .vscode/c_cpp_properties.json | 19 ++++ .vscode/launch.json | 10 ++ .vscode/settings.json | 18 ++++ .vscode/tasks.json | 83 ++++++++++++++++ CMakeLists.txt | 8 ++ PROJECT_SETUP.md | 158 +++++++++++++++++++++++++++++++ main/CMakeLists.txt | 7 ++ main/app_config.cpp | 33 +++++++ main/app_config.hpp | 22 +++++ main/display_manager.cpp | 44 +++++++++ main/display_manager.hpp | 31 ++++++ main/lv_conf.h | 45 +++++++++ main/main.c | 6 ++ main/main.cpp | 22 +++++ main/web_server.cpp | 104 ++++++++++++++++++++ main/web_server.hpp | 24 +++++ main/wifi_manager.cpp | 163 ++++++++++++++++++++++++++++++++ main/wifi_manager.hpp | 27 ++++++ setup_esp_env.sh | 30 ++++++ 24 files changed, 1032 insertions(+) create mode 100644 .clangd create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitignore create mode 100644 .vscode/ESP_SETUP.md create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 PROJECT_SETUP.md create mode 100644 main/CMakeLists.txt create mode 100644 main/app_config.cpp create mode 100644 main/app_config.hpp create mode 100644 main/display_manager.cpp create mode 100644 main/display_manager.hpp create mode 100644 main/lv_conf.h create mode 100644 main/main.c create mode 100644 main/main.cpp create mode 100644 main/web_server.cpp create mode 100644 main/web_server.hpp create mode 100644 main/wifi_manager.cpp create mode 100644 main/wifi_manager.hpp create mode 100644 setup_esp_env.sh diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..437f255 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Remove: [-f*, -m*] diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..dafb8ad --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +ARG DOCKER_TAG=latest +FROM espressif/idf:${DOCKER_TAG} + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN apt-get update -y && apt-get install udev -y + +RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc + +ENTRYPOINT [ "/opt/esp/entrypoint.sh" ] + +CMD ["/bin/bash", "-c"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..246b79f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +{ + "name": "ESP-IDF QEMU", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension", + "espressif.esp-idf-web" + ] + } + }, + "runArgs": ["--privileged"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7805557 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Directory metadata +.directory + +# Temporary files +*~ +*.swp +*.swo +*.bak +*.tmp + +# Log files +*.log + +# Build artifacts and directories +**/build/ +build/ +*.o +*.a +*.out +*.exe # For any host-side utilities compiled on Windows + +# ESP-IDF specific build outputs +*.bin +*.elf +*.map +flasher_args.json # Generated in build directory +sdkconfig.old +sdkconfig + +# ESP-IDF dependencies +# For older versions or manual component management +/components/.idf/ +**/components/.idf/ +# For modern ESP-IDF component manager +managed_components/ +# If ESP-IDF tools are installed/referenced locally to the project +.espressif/ + +# CMake generated files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake + +# Python environment files +*.pyc +*.pyo +*.pyd +__pycache__/ +*.egg-info/ +dist/ + +# Virtual environment folders +venv/ +.venv/ +env/ + +# Language Servers +.clangd/ +.ccls-cache/ +compile_commands.json + +# Windows specific +Thumbs.db +ehthumbs.db +Desktop.ini + +# User-specific configuration files +*.user +*.workspace # General workspace files, can be from various tools +*.suo # Visual Studio Solution User Options +*.sln.docstates # Visual Studio diff --git a/.vscode/ESP_SETUP.md b/.vscode/ESP_SETUP.md new file mode 100644 index 0000000..2b71e5b --- /dev/null +++ b/.vscode/ESP_SETUP.md @@ -0,0 +1,66 @@ +# ESP-IDF Project Setup Guide + +Your zonebridge project is configured to use ESP-IDF v5.5.1 located at `/Users/brent/esp/v5.5.1/esp-idf`. + +## Environment Setup + +### Option 1: Per-Session Setup (Quick) +Run this in your terminal before building: +```bash +export IDF_PATH=/Users/brent/esp/v5.5.1/esp-idf +export IDF_TOOLS_PATH=/Users/brent/.espressif +. $IDF_PATH/export.sh +``` + +### Option 2: Permanent Shell Profile Setup +Add this to your `~/.zshrc` file: +```bash +# ESP-IDF Setup +export IDF_PATH="/Users/brent/esp/v5.5.1/esp-idf" +export IDF_TOOLS_PATH="/Users/brent/.espressif" + +# Source ESP-IDF export script +if [ -f "$IDF_PATH/export.sh" ]; then + . "$IDF_PATH/export.sh" +fi +``` + +Then reload your shell: +```bash +source ~/.zshrc +``` + +## Project Configuration + +Your project is already configured with: +- **ESP-IDF Setup**: `/Users/brent/esp/v5.5.1/esp-idf` +- **Target**: ESP32-S3 +- **Toolchain**: xtensa-esp-elf-gcc (in `~/.espressif/tools/`) +- **Clangd**: Located at `~/.espressif/tools/esp-clang/` + +## Building the Project + +Once environment variables are set, you can: + +1. **Build with VS Code**: Use Command Palette (`Cmd+Shift+P`) → "ESP-IDF: Build Project" +2. **Build from Terminal**: + ```bash + cd /Users/brent/zonebridge/zonebridge + idf.py build + ``` + +## Flashing & Monitoring + +```bash +idf.py flash monitor # Flash and monitor output +idf.py menuconfig # Open SDK configuration +idf.py clean # Clean build artifacts +``` + +## Troubleshooting + +If you see "IDF_PATH not set" errors: +1. Verify the export.sh script was sourced +2. Check that `echo $IDF_PATH` returns `/Users/brent/esp/v5.5.1/esp-idf` +3. Run `idf.py doctor` to diagnose any issues + diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..11caddf --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "/Users/brent/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc", + "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "includePath": [ + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3694ae4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cb658b0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "clangd.path": "/Users/brent/.espressif/tools/esp-clang/esp-19.1.2_20250312/esp-clang/bin/clangd", + "clangd.arguments": [ + "--background-index", + "--query-driver=**", + "--compile-commands-dir=/Users/brent/zonebridge/zonebridge/build" + ], + "idf.currentSetup": "/Users/brent/esp/v5.5.1/esp-idf", + "idf.openOcdConfigs": [ + "board/esp32s3-builtin.cfg" + ], + "idf.customExtraVars": { + "IDF_TARGET": "esp32s3" + }, + "idf.port": "/dev/tty.usbserial-210", + "idf.flashType": "UART" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..8d1e7c9 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,83 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "ESP-IDF: Build", + "type": "shell", + "command": "bash", + "args": [ + "-c", + "source ${workspaceFolder}/setup_esp_env.sh && idf.py build" + ], + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared" + } + }, + { + "label": "ESP-IDF: Clean", + "type": "shell", + "command": "bash", + "args": [ + "-c", + "source ${workspaceFolder}/setup_esp_env.sh && idf.py clean" + ], + "presentation": { + "reveal": "always", + "focus": false, + "panel": "shared" + } + }, + { + "label": "ESP-IDF: Flash & Monitor", + "type": "shell", + "command": "bash", + "args": [ + "-c", + "source ${workspaceFolder}/setup_esp_env.sh && idf.py flash monitor" + ], + "presentation": { + "reveal": "always", + "focus": true, + "panel": "new" + } + }, + { + "label": "ESP-IDF: Menuconfig", + "type": "shell", + "command": "bash", + "args": [ + "-c", + "source ${workspaceFolder}/setup_esp_env.sh && idf.py menuconfig" + ], + "presentation": { + "reveal": "always", + "focus": true, + "panel": "new" + } + }, + { + "label": "ESP-IDF: Size Analysis", + "type": "shell", + "command": "bash", + "args": [ + "-c", + "source ${workspaceFolder}/setup_esp_env.sh && idf.py size" + ], + "presentation": { + "reveal": "always", + "focus": false, + "panel": "shared" + } + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5cec6fa --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(zonebridge) \ No newline at end of file diff --git a/PROJECT_SETUP.md b/PROJECT_SETUP.md new file mode 100644 index 0000000..be8605b --- /dev/null +++ b/PROJECT_SETUP.md @@ -0,0 +1,158 @@ +# ZoneBridge - ESP32-S3 Touch LCD Project Setup + +## Project Overview +This is a C++ ESP-IDF project for the Waveshare ESP32-S3-Touch-LCD 4.3" display with the following features: +- **C++ Framework** with proper C++ exception handling +- **FreeRTOS** for task management +- **LVGL** for UI/graphics on the 800x480 touch display +- **WiFi** connectivity +- **HTTP Web Server** for remote control/monitoring +- **Touch Input** support for the capacitive touch panel + +## Project Structure +``` +main/ +├── main.cpp # Entry point (C++) +├── app_config.hpp/cpp # Application initialization and configuration +├── display_manager.hpp/cpp # LVGL display handling and UI management +├── wifi_manager.hpp/cpp # WiFi connectivity management +├── web_server.hpp/cpp # HTTP web server implementation +├── lv_conf.h # LVGL configuration +└── idf_component.yml # Component dependencies +``` + +## Configuration + +### WiFi Credentials +Edit these constants in [app_config.hpp](app_config.hpp): +```cpp +#define DEFAULT_SSID "YOUR_SSID" +#define DEFAULT_PASSWORD "YOUR_PASSWORD" +``` + +### Display Configuration +The display parameters are configured in [app_config.hpp](app_config.hpp): +- **Resolution**: 800x480 +- **Color Depth**: 16-bit RGB565 +- **Display Driver**: LVGL (software stack) + +### Web Server +- **Default Port**: 80 +- **Endpoints**: + - `GET /` - HTML dashboard + - `GET /api/status` - JSON status endpoint + +## Building the Project + +### Method 1: VS Code Tasks (Recommended) +1. Open Command Palette: `Cmd+Shift+P` +2. Run "Tasks: Run Task" +3. Select "ESP-IDF: Build" + +### Method 2: Terminal +```bash +cd /Users/brent/zonebridge/zonebridge +source ./setup_esp_env.sh +idf.py build +``` + +## Common Commands + +```bash +# Build the project +idf.py build + +# Clean build artifacts +idf.py clean + +# Configure via menuconfig +idf.py menuconfig + +# Flash to device and monitor +idf.py flash monitor + +# Monitor serial output +idf.py monitor + +# Analyze binary size +idf.py size +``` + +## Hardware Notes + +### Waveshare ESP32-S3-Touch-LCD 4.3" +- **Display**: 4.3" 800x480 RGB LCD +- **Touch Input**: Capacitive touchscreen +- **Connectivity**: Built-in WiFi and Bluetooth +- **Ports**: USB-C for programming and power + +### Display Connection +The display is typically connected via: +- 16-bit parallel RGB interface (via LCDC peripheral) +- SPI interface (alternative, if RGB not used) +- I2C for touch controller (CST816) + +### Touch Controller +- **Model**: CST816S (capacitive touchpad controller) +- **Interface**: I2C (typically pins 19/20 on ESP32-S3) +- **Interrupt**: GPIO pin for touch interrupt + +## Implementation Tasks + +### Phase 1: ✓ Project Structure +- [x] C++ conversion +- [x] FreeRTOS integration +- [x] Component hierarchy +- [x] Basic LVGL setup + +### Phase 2: Display Driver +- [ ] RGB LCD driver initialization +- [ ] Touch input driver (CST816 I2C) +- [ ] Frame buffer management +- [ ] Display refresh loop + +### Phase 3: UI Development +- [ ] Main dashboard UI +- [ ] Status displays +- [ ] Control buttons +- [ ] Menu system + +### Phase 4: WiFi & Web +- [ ] WiFi connection handling +- [ ] Web dashboard enhancement +- [ ] REST API endpoints +- [ ] WebSocket support (optional) + +## Next Steps + +1. **Configure WiFi**: Update WiFi credentials in [app_config.hpp](app_config.hpp) +2. **Build & Test**: Run build task to verify compilation +3. **Flash Device**: Use `idf.py flash monitor` to program device +4. **Display Driver**: Implement actual display hardware interface +5. **Touch Input**: Add touch controller driver + +## Troubleshooting + +### Build Errors Related to Components +```bash +# Rebuild component cache +rm -rf build +idf.py fullclean +idf.py build +``` + +### LVGL Compilation Issues +- Ensure LVGL component is installed: `idf.py component-manager create-manifest` +- Check `lv_conf.h` matches your requirements + +### WiFi Connection Issues +- Verify SSID/password in `app_config.hpp` +- Check WiFi event handling in `wifi_manager.cpp` +- Monitor UART output: `idf.py monitor` + +## Resources + +- [ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/) +- [LVGL Documentation](https://docs.lvgl.io/) +- [Waveshare Product Page](https://www.waveshare.com/esp32-s3-touch-lcd-4.3.htm?sku=25948) +- [ESP32-S3 Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..8abb694 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register(SRCS "main.cpp" + "app_config.cpp" + "wifi_manager.cpp" + "web_server.cpp" + "display_manager.cpp" + INCLUDE_DIRS "." + REQUIRES freertos esp_wifi esp_http_server esp_event esp_netif nvs_flash) \ No newline at end of file diff --git a/main/app_config.cpp b/main/app_config.cpp new file mode 100644 index 0000000..d70c065 --- /dev/null +++ b/main/app_config.cpp @@ -0,0 +1,33 @@ +#include "app_config.hpp" +#include "wifi_manager.hpp" +#include "display_manager.hpp" +#include "web_server.hpp" +#include + +static const char *TAG = "app_config"; + +void app_init(void) +{ + ESP_LOGI(TAG, "Initializing application components"); + + // Initialize display (LVGL) + ESP_LOGI(TAG, "Initializing display..."); + DisplayManager& display = DisplayManager::getInstance(); + display.init(); + display.createMainScreen(); + display.updateStatusText("Initializing WiFi..."); + + // Initialize WiFi + ESP_LOGI(TAG, "Initializing WiFi..."); + WiFiManager& wifi = WiFiManager::getInstance(); + wifi.init(DEFAULT_SSID, DEFAULT_PASSWORD); + wifi.connect(); + + // Initialize Web Server + ESP_LOGI(TAG, "Starting web server..."); + WebServer& web = WebServer::getInstance(); + web.start(WEB_SERVER_PORT); + + ESP_LOGI(TAG, "Application initialized successfully"); + display.updateStatusText("Ready"); +} diff --git a/main/app_config.hpp b/main/app_config.hpp new file mode 100644 index 0000000..5867c69 --- /dev/null +++ b/main/app_config.hpp @@ -0,0 +1,22 @@ +#ifndef APP_CONFIG_HPP +#define APP_CONFIG_HPP + +#include + +// WiFi Configuration +#define DEFAULT_SSID "LittlePeppers" +#define DEFAULT_PASSWORD "SoliDeoGloria" + +// Display Configuration (Waveshare ESP32-S3-Touch-LCD 4.3") +#define DISPLAY_WIDTH 800 +#define DISPLAY_HEIGHT 480 +#define DISPLAY_COLOR_DEPTH 16 + +// Web Server Configuration +#define WEB_SERVER_PORT 80 +#define WEB_SERVER_STACK_SIZE 4096 + +// Initialize all application components +void app_init(void); + +#endif // APP_CONFIG_HPP diff --git a/main/display_manager.cpp b/main/display_manager.cpp new file mode 100644 index 0000000..f3f8ecb --- /dev/null +++ b/main/display_manager.cpp @@ -0,0 +1,44 @@ +#include "display_manager.hpp" +#include + +static const char *TAG = "display_manager"; + +DisplayManager DisplayManager::instance; + +DisplayManager& DisplayManager::getInstance() +{ + return instance; +} + +void DisplayManager::init() +{ + ESP_LOGI(TAG, "Initializing display (800x480)"); + // TODO: Initialize actual display hardware + // - RGB LCD configuration + // - SPI/I2C initialization + // - GPIO setup + ESP_LOGI(TAG, "Display initialized successfully"); +} + +void DisplayManager::update() +{ + // TODO: Update display with frame buffer +} + +void DisplayManager::createMainScreen() +{ + ESP_LOGI(TAG, "Creating main screen"); + // TODO: LVGL screen creation +} + +void DisplayManager::createStatusBar() +{ + ESP_LOGI(TAG, "Creating status bar"); + // TODO: Status bar UI +} + +void DisplayManager::updateStatusText(const char* text) +{ + ESP_LOGI(TAG, "Status: %s", text); + // TODO: Update LVGL label +} diff --git a/main/display_manager.hpp b/main/display_manager.hpp new file mode 100644 index 0000000..6aea8be --- /dev/null +++ b/main/display_manager.hpp @@ -0,0 +1,31 @@ +#ifndef DISPLAY_MANAGER_HPP +#define DISPLAY_MANAGER_HPP + +// #include + +class DisplayManager { +public: + static DisplayManager& getInstance(); + + void init(); + void update(); + // lv_disp_t* getDisplay() const; + + // Helper methods to create common UI elements + void createMainScreen(); + void createStatusBar(); + void updateStatusText(const char* text); + +private: + DisplayManager() = default; + static DisplayManager instance; + // lv_disp_t* display = nullptr; + // lv_obj_t* main_screen = nullptr; + // lv_obj_t* status_label = nullptr; + + // Display callbacks + // static void flush_cb(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p); + // static void read_cb(lv_indev_drv_t* indev_drv, lv_indev_data_t* data); +}; + +#endif // DISPLAY_MANAGER_HPP diff --git a/main/lv_conf.h b/main/lv_conf.h new file mode 100644 index 0000000..77db680 --- /dev/null +++ b/main/lv_conf.h @@ -0,0 +1,45 @@ +#ifndef LV_CONF_H +#define LV_CONF_H + +/* Display */ +#define LV_HOR_RES_MAX 800 +#define LV_VER_RES_MAX 480 +#define LV_COLOR_DEPTH 16 +#define LV_COLOR_16_SWAP 0 +#define LV_COLOR_SCREEN_ON_BW_THRESH 127 + +/* Memory */ +#define LV_MEM_CUSTOM 0 +#define LV_MEM_SIZE (48 * 1024) +#define LV_MEM_ADR 0 +#define LV_MEM_AUTO_DEFRAG 1 + +/* Drawing */ +#define LV_DISP_DEF_REFR_PERIOD 30 +#define LV_INDEV_DEF_READ_PERIOD 30 + +/* Logging */ +#define LV_USE_LOG 1 +#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN +#define LV_LOG_MEM_USAGE 1 +#define LV_LOG_TIMER 1 + +/* LVGL Version */ +#define LV_VERSION_CHECK 1 + +/* Extended features */ +#define LV_USE_DRAW_SIMPLE_IMG 1 +#define LV_USE_DRAW_TRANSFORM 1 +#define LV_USE_DRAW_DITHER 1 +#define LV_USE_DRAW_MASK 1 + +/* Themes */ +#define LV_USE_THEME_DEFAULT 1 + +/* Image decoder */ +#define LV_USE_IMG_DECODER_EXTERNAL 1 + +/* Touch input */ +#define LV_USE_TOUCH_INPUT 1 + +#endif diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..ada900d --- /dev/null +++ b/main/main.c @@ -0,0 +1,6 @@ +#include + +void app_main(void) +{ + +} \ No newline at end of file diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..e57ca89 --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include "app_config.hpp" + +static const char *TAG = "main"; + +extern "C" void app_main(void) +{ + ESP_LOGI(TAG, "ZoneBridge Application Starting"); + ESP_LOGI(TAG, "ESP32-S3 Touch LCD 4.3\" (Waveshare)"); + + // Initialize application components + app_init(); + + // Main application loop + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + //ESP_LOGI(TAG, "heartbeat"); + } +} diff --git a/main/web_server.cpp b/main/web_server.cpp new file mode 100644 index 0000000..208c162 --- /dev/null +++ b/main/web_server.cpp @@ -0,0 +1,104 @@ +#include "web_server.hpp" +#include "app_config.hpp" +#include +#include + +static const char *TAG = "web_server"; + +WebServer WebServer::instance; + +// Root handler - serves a simple dashboard +static esp_err_t rootHandler(httpd_req_t* req) +{ + const char* response = R"( + + + + ZoneBridge Dashboard + + + + +
+

ZoneBridge Control Panel

+
System Online
+

ESP32-S3 Touch LCD 4.3" is running

+
+ + + )"; + + httpd_resp_set_type(req, "text/html"); + return httpd_resp_send(req, response, strlen(response)); +} + +// API handler for status +static esp_err_t apiStatusHandler(httpd_req_t* req) +{ + const char* json_response = R"({"status":"online","uptime":0})"; + httpd_resp_set_type(req, "application/json"); + return httpd_resp_send(req, json_response, strlen(json_response)); +} + +WebServer& WebServer::getInstance() +{ + return instance; +} + +void WebServer::start(int port) +{ + if (server != nullptr) { + ESP_LOGW(TAG, "Web server already running"); + return; + } + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = port; + config.stack_size = WEB_SERVER_STACK_SIZE; + config.max_uri_handlers = 16; + + if (httpd_start(&server, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start web server"); + return; + } + + // Register URL handlers + httpd_uri_t root = { + .uri = "/", + .method = HTTP_GET, + .handler = rootHandler, + .user_ctx = nullptr + }; + httpd_register_uri_handler(server, &root); + + httpd_uri_t api_status = { + .uri = "/api/status", + .method = HTTP_GET, + .handler = apiStatusHandler, + .user_ctx = nullptr + }; + httpd_register_uri_handler(server, &api_status); + + ESP_LOGI(TAG, "Web server started on port %d", port); +} + +void WebServer::stop() +{ + if (server != nullptr) { + httpd_stop(server); + server = nullptr; + ESP_LOGI(TAG, "Web server stopped"); + } +} + +bool WebServer::isRunning() const +{ + return server != nullptr; +} diff --git a/main/web_server.hpp b/main/web_server.hpp new file mode 100644 index 0000000..01e9fc2 --- /dev/null +++ b/main/web_server.hpp @@ -0,0 +1,24 @@ +#ifndef WEB_SERVER_HPP +#define WEB_SERVER_HPP + +#include + +class WebServer { +public: + static WebServer& getInstance(); + + void start(int port = 80); + void stop(); + bool isRunning() const; + +private: + WebServer() = default; + static WebServer instance; + httpd_handle_t server = nullptr; + + // HTTP request handlers + static esp_err_t handleRootRequest(httpd_req_t* req); + static esp_err_t handleApiRequest(httpd_req_t* req); +}; + +#endif // WEB_SERVER_HPP diff --git a/main/wifi_manager.cpp b/main/wifi_manager.cpp new file mode 100644 index 0000000..fa54a76 --- /dev/null +++ b/main/wifi_manager.cpp @@ -0,0 +1,163 @@ +#include "wifi_manager.hpp" +#include +#include +#include +#include +#include + +static const char *TAG = "wifi_manager"; + +WiFiManager WiFiManager::instance; + +WiFiManager& WiFiManager::getInstance() +{ + return instance; +} + +void WiFiManager::init(const char* ssid, const char* password) +{ + ESP_LOGI(TAG, "Initializing WiFi with SSID: %s", ssid); + + // Step 1: Initialize NVS (needed for WiFi storage) + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGI(TAG, "Erasing NVS flash..."); + nvs_flash_erase(); + ret = nvs_flash_init(); + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "NVS flash init failed: %s", esp_err_to_name(ret)); + return; + } + + // Step 2: Create event loop (needed for WiFi events) + ret = esp_event_loop_create_default(); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "Event loop creation failed: %s", esp_err_to_name(ret)); + return; + } + + // Step 3: Initialize network interface (needed for WiFi) + ret = esp_netif_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "NetIF init failed: %s", esp_err_to_name(ret)); + return; + } + + // Step 4: Create default WiFi station interface + esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); + if (!sta_netif) { + ESP_LOGE(TAG, "Failed to create default WiFi station interface"); + return; + } + + // Step 5: Initialize WiFi driver + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ret = esp_wifi_init(&cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "WiFi init failed: %s", esp_err_to_name(ret)); + return; + } + + // Step 6: Set WiFi configuration + wifi_config_t wifi_config = {}; + strncpy((char*)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1); + strncpy((char*)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1); + wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; + wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; + wifi_config.sta.threshold.rssi = -127; + + ret = esp_wifi_set_mode(WIFI_MODE_STA); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set WiFi mode failed: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set WiFi config failed: %s", esp_err_to_name(ret)); + return; + } + + ESP_LOGI(TAG, "WiFi manager initialized successfully"); +} + +void WiFiManager::connect() +{ + ESP_LOGI(TAG, "Connecting to WiFi..."); + + // Register event handlers + esp_err_t ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WiFiManager::eventHandler, nullptr); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to register WiFi event handler: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &WiFiManager::eventHandler, nullptr); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_wifi_start(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "WiFi start failed: %s", esp_err_to_name(ret)); + return; + } + + ESP_LOGI(TAG, "WiFi start initiated"); +} + +void WiFiManager::disconnect() +{ + if (esp_wifi_stop() == ESP_OK) { + connected = false; + ESP_LOGI(TAG, "WiFi disconnected"); + } +} + +bool WiFiManager::isConnected() const +{ + return connected; +} + +const char* WiFiManager::getIpAddress() const +{ + return ip_address; +} + +void WiFiManager::eventHandler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + WiFiManager& self = getInstance(); + + if (event_base == WIFI_EVENT) { + if (event_id == WIFI_EVENT_STA_START) { + ESP_LOGI(TAG, "WiFi started, requesting connection..."); + esp_err_t conn_ret = esp_wifi_connect(); + if (conn_ret != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_connect failed on STA_START: %s", esp_err_to_name(conn_ret)); + } + } else if (event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGW(TAG, "WiFi disconnected, retrying..."); + self.connected = false; + esp_err_t conn_ret = esp_wifi_connect(); + if (conn_ret != ESP_OK) { + ESP_LOGE(TAG, "esp_wifi_connect retry failed: %s", esp_err_to_name(conn_ret)); + } + } + } else if (event_base == IP_EVENT) { + if (event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; + uint32_t ip = event->ip_info.ip.addr; + snprintf(self.ip_address, sizeof(self.ip_address), + "%u.%u.%u.%u", + (unsigned int)((ip) & 0xFF), + (unsigned int)((ip >> 8) & 0xFF), + (unsigned int)((ip >> 16) & 0xFF), + (unsigned int)((ip >> 24) & 0xFF)); + self.connected = true; + ESP_LOGI(TAG, "WiFi connected! IP: %s", self.ip_address); + } + } +} diff --git a/main/wifi_manager.hpp b/main/wifi_manager.hpp new file mode 100644 index 0000000..df2fe96 --- /dev/null +++ b/main/wifi_manager.hpp @@ -0,0 +1,27 @@ +#ifndef WIFI_MANAGER_HPP +#define WIFI_MANAGER_HPP + +#include +#include + +class WiFiManager { +public: + static WiFiManager& getInstance(); + + void init(const char* ssid, const char* password); + void connect(); + void disconnect(); + bool isConnected() const; + const char* getIpAddress() const; + +private: + WiFiManager() = default; + static WiFiManager instance; + bool connected = false; + char ip_address[16] = {0}; + + static void eventHandler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data); +}; + +#endif // WIFI_MANAGER_HPP diff --git a/setup_esp_env.sh b/setup_esp_env.sh new file mode 100644 index 0000000..35c27e1 --- /dev/null +++ b/setup_esp_env.sh @@ -0,0 +1,30 @@ +#!/bin/zsh +# ESP-IDF Environment Setup Script for zonebridge project +# Source this script from your terminal: . ./setup_esp_env.sh + +export IDF_PATH="/Users/brent/esp/v5.5.1/esp-idf" +export IDF_TOOLS_PATH="/Users/brent/.espressif" +export IDF_TARGET="esp32s3" + +echo "Setting up ESP-IDF environment..." +echo "IDF_PATH: $IDF_PATH" +echo "IDF_TOOLS_PATH: $IDF_TOOLS_PATH" +echo "IDF_TARGET: $IDF_TARGET" + +# Source the ESP-IDF export script +if [ -f "$IDF_PATH/export.sh" ]; then + . "$IDF_PATH/export.sh" + echo "✓ ESP-IDF environment initialized successfully" +else + echo "✗ Error: ESP-IDF export script not found at $IDF_PATH/export.sh" + return 1 +fi + +# Verify the setup +if command -v idf.py &> /dev/null; then + echo "✓ idf.py is available" + echo "" + echo "Ready to build! Try: idf.py build" +else + echo "⚠ Warning: idf.py not found in PATH" +fi