commit c1075082b3604f051d3bfc245d5823327782e84c Author: drjones Date: Wed May 20 10:04:24 2026 -0700 chore: import local project into Gitea diff --git a/._.gitignore b/._.gitignore new file mode 100644 index 0000000..613fad7 Binary files /dev/null and b/._.gitignore differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81ca08a --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# OS / tooling +.DS_Store +Thumbs.db + +# Editors +.cursor/ + +# Secrets +.env +.env.* +!.env.example +!.env.template + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ + +# Node / frontend +node_modules/ +dist/ + +# Typical embedded / tooling noise +*.log + +# Builds (adjust per subtree if needed) +**/build/.ninja_deps +**/build/.ninja_log +build/ + +# Vendor SDK checkouts +esp-idf/ +**/esp-idf/ +managed_components/ + +# Local generated configuration +sdkconfig +sdkconfig.old diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..373f319 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "cmake.sourceDirectory": "F:/dev_shit/p4 chatbot 83125/p4_gemini_chat", + "idf.espIdfPathWin": "C:/Users/india/Desktop/esp-idf/", + "idf.toolsPathWin": "C:\\Users\\india\\.espressif", + "idf.pythonInstallPath": "C:\\Users\\india\\.espressif\\tools\\idf-python\\3.11.2\\python.exe", + "idf.portWin": "COM3" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5b1f95 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# p4 Gemini chat (ESP-IDF) + +Parent tree for Gemini-enabled UI on Espressif’s **ESP32-P4-class** toolchain. + +Main firmware: **`p4_gemini_chat/`** + +| Path | Notes | +|------|-------| +| `p4_gemini_chat/` | CMake + `main/` sources, `sdkconfig`, `managed_components` | +| `components/` | Shared component libraries | +| `ai brains/` | Prompt / persona assets | +| `build/` | Local IDF artifacts (keep untracked) | + +## Build flow + +Install **ESP-IDF** matching Espressif P4 toolchain requirements, activate environment, then: + +```bash +cd p4_gemini_chat +idf.py set-target esp32p4 # confirm precise target alias with Espressif release notes +idf.py fullclean build flash monitor +``` + +Export `GEMINI_API_KEY` (or whichever env var main sources expect) securely—never bake keys into firmware images. diff --git a/components/bsp/CMakeLists.txt b/components/bsp/CMakeLists.txt new file mode 100644 index 0000000..205a99b --- /dev/null +++ b/components/bsp/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "bsp.c" + INCLUDE_DIRS ".") diff --git a/components/bsp/bsp.c b/components/bsp/bsp.c new file mode 100644 index 0000000..2c109de --- /dev/null +++ b/components/bsp/bsp.c @@ -0,0 +1,23 @@ +#include "bsp/esp-bsp.h" +#include "bsp.h" + +void bsp_display_start(void) +{ + bsp_display_cfg_t cfg = { + .lvgl_port_cfg = ESP_LVGL_PORT_INIT_CONFIG(), + .buffer_size = BSP_LCD_DRAW_BUFF_SIZE, + .double_buffer = BSP_LCD_DRAW_BUFF_DOUBLE, + .flags = { + .buff_dma = true, + .buff_spiram = false, + .sw_rotate = false, + }}; + bsp_display_start_with_config(&cfg); + bsp_display_backlight_on(); +} + +void bsp_touch_init(void) +{ + // The touch driver is initialized as part of bsp_display_start() + // in the esp-bsp component, so this function can be left empty. +} diff --git a/components/bsp/bsp.h b/components/bsp/bsp.h new file mode 100644 index 0000000..0b9792a --- /dev/null +++ b/components/bsp/bsp.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void bsp_display_start(void); +void bsp_touch_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/p4_gemini_chat/.vscode/settings.json b/p4_gemini_chat/.vscode/settings.json new file mode 100644 index 0000000..7a4617b --- /dev/null +++ b/p4_gemini_chat/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "idf.openOcdConfigs": [ + "board/esp32p4-builtin.cfg" + ] +} \ No newline at end of file diff --git a/p4_gemini_chat/CMakeLists.txt b/p4_gemini_chat/CMakeLists.txt new file mode 100644 index 0000000..411d410 --- /dev/null +++ b/p4_gemini_chat/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(p4_gemini_chat) diff --git a/p4_gemini_chat/Kconfig.projbuild b/p4_gemini_chat/Kconfig.projbuild new file mode 100644 index 0000000..51d9dc1 --- /dev/null +++ b/p4_gemini_chat/Kconfig.projbuild @@ -0,0 +1,13 @@ +menu "Gemini Chat Config" +config GEMINI_API_KEY + string "Gemini API Key" + help + Paste the API key from Google AI Studio. +config WIFI_SSID + string "WiFi SSID" +config WIFI_PASS + string "WiFi Password" +config GEMINI_MODEL + string "Gemini model name" + default "gemini-2.5-flash" +endmenu diff --git a/p4_gemini_chat/build.bat b/p4_gemini_chat/build.bat new file mode 100644 index 0000000..eba28b9 --- /dev/null +++ b/p4_gemini_chat/build.bat @@ -0,0 +1,2 @@ +call C:/Users/india/Desktop/esp-idf/export.bat +idf.py build diff --git a/p4_gemini_chat/dependencies.lock b/p4_gemini_chat/dependencies.lock new file mode 100644 index 0000000..12d3f89 --- /dev/null +++ b/p4_gemini_chat/dependencies.lock @@ -0,0 +1,18 @@ +dependencies: + idf: + source: + type: idf + version: 5.4.1 + lvgl/lvgl: + component_hash: 948bff879a345149b83065535bbc4a026ce9f47498a22881e432a264b9098015 + dependencies: [] + source: + registry_url: https://components.espressif.com/ + type: service + version: 8.3.11 +direct_dependencies: +- idf +- lvgl/lvgl +manifest_hash: ba5fbe24af6b54c310e2fcf43426ef3f04e9de1ac47206b696b1bb81856e9340 +target: esp32 +version: 2.0.0 diff --git a/p4_gemini_chat/main/CMakeLists.txt b/p4_gemini_chat/main/CMakeLists.txt new file mode 100644 index 0000000..110bbe9 --- /dev/null +++ b/p4_gemini_chat/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "main.c" + REQUIRES esp_http_client esp_wifi nvs_flash json lvgl esp_timer +) diff --git a/p4_gemini_chat/main/idf_component.yml b/p4_gemini_chat/main/idf_component.yml new file mode 100644 index 0000000..f743fa6 --- /dev/null +++ b/p4_gemini_chat/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + ## Required IDF version + idf: + version: '>=4.1.0' + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true + lvgl/lvgl: ~8.3.11 diff --git a/p4_gemini_chat/main/main.c b/p4_gemini_chat/main/main.c new file mode 100644 index 0000000..fa38457 --- /dev/null +++ b/p4_gemini_chat/main/main.c @@ -0,0 +1,201 @@ +#include "esp_event.h" +#include "nvs_flash.h" +#include "esp_wifi.h" +#include "esp_netif.h" +#include "esp_http_client.h" +#include "esp_crt_bundle.h" +#include "cJSON.h" +#include "lvgl.h" +#include "esp_timer.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "bsp.h" +#include + +#define TAG "P4-GEMINI" + +// ----- Kconfig bindings +#define WIFI_SSID CONFIG_WIFI_SSID +#define WIFI_PASS CONFIG_WIFI_PASS +#define GEMINI_API_KEY CONFIG_GEMINI_API_KEY +#define GEMINI_MODEL CONFIG_GEMINI_MODEL + +// Simple UI elements +static lv_obj_t *ta; // text area (user input) +static lv_obj_t *logbox; // label for responses + +// Cheap LVGL tick (adjust to your BSP pattern) +static void lv_tick_cb(void* arg){ lv_tick_inc(5); } + +static void wifi_init(void) { + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + wifi_config_t wifi_config = {0}; + strncpy((char*)wifi_config.sta.ssid, WIFI_SSID, sizeof(wifi_config.sta.ssid)-1); + strncpy((char*)wifi_config.sta.password, WIFI_PASS, sizeof(wifi_config.sta.password)-1); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_connect()); +} + +// Build {"contents":[{"role":"user","parts":[{"text":"..."}]}]} +static char* build_gemini_body(const char* user_text) { + cJSON *root = cJSON_CreateObject(); + cJSON *contents = cJSON_AddArrayToObject(root, "contents"); + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "role", "user"); + cJSON *parts = cJSON_AddArrayToObject(item, "parts"); + cJSON *part0 = cJSON_CreateObject(); + cJSON_AddStringToObject(part0, "text", user_text); + cJSON_AddItemToArray(parts, part0); + cJSON_AddItemToArray(contents, item); + char *out = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + return out; +} + +// Parse response.response.candidates[0].content.parts[0].text +static char* parse_gemini_reply(const char* json) { + cJSON *root = cJSON_Parse(json); + if(!root) return NULL; + cJSON *resp = cJSON_GetObjectItem(root, "candidates"); + if(!cJSON_IsArray(resp) || cJSON_GetArraySize(resp)==0){ cJSON_Delete(root); return NULL; } + cJSON *c0 = cJSON_GetArrayItem(resp, 0); + cJSON *content = cJSON_GetObjectItem(c0, "content"); + cJSON *parts = content ? cJSON_GetObjectItem(content, "parts") : NULL; + if(!parts || !cJSON_IsArray(parts) || cJSON_GetArraySize(parts)==0){ cJSON_Delete(root); return NULL; } + cJSON *p0 = cJSON_GetArrayItem(parts, 0); + cJSON *text = cJSON_GetObjectItem(p0, "text"); + char *dup = text && cJSON_IsString(text) ? strdup(text->valuestring) : NULL; + cJSON_Delete(root); + return dup; +} + +static esp_err_t http_event(esp_http_client_event_t *evt){ + return ESP_OK; +} + +static char* gemini_generate(const char* prompt){ + // Use TLS bundle (no manual root CA pin) + esp_http_client_config_t cfg = { + .url = "https://generativelanguage.googleapis.com/v1beta/models/" GEMINI_MODEL ":generateContent?key=" GEMINI_API_KEY, + .method = HTTP_METHOD_POST, + .crt_bundle_attach = esp_crt_bundle_attach, + .event_handler = http_event, + .timeout_ms = 20000, + }; + esp_http_client_handle_t client = esp_http_client_init(&cfg); + + char *body = build_gemini_body(prompt); + esp_http_client_set_header(client, "Content-Type", "application/json"); + ESP_ERROR_CHECK(esp_http_client_open(client, strlen(body))); + int w = esp_http_client_write(client, body, strlen(body)); + free(body); + if(w <= 0){ esp_http_client_cleanup(client); return strdup("[write failed]"); } + + esp_http_client_fetch_headers(client); + int status = esp_http_client_get_status_code(client); + ESP_LOGI(TAG, "HTTP Status = %d", status); + + int len = esp_http_client_get_content_length(client); + if(len < 0) len = 4096; // fallback + char *buf = calloc(1, len+1); + int r = esp_http_client_read_response(client, buf, len); + esp_http_client_close(client); + esp_http_client_cleanup(client); + + if(r <= 0){ + free(buf); + return strdup("[no response]"); + } + + char *reply = parse_gemini_reply(buf); + if(!reply) { + ESP_LOGE(TAG, "Failed to parse JSON response: %s", buf); + reply = strdup("[parse error]"); + } + + free(buf); + return reply; +} + +// Task to call Gemini API +static void gemini_task(void *pvParameters) { + char *prompt = (char *)pvParameters; + char *ans = gemini_generate(prompt); + + // Update UI from the main thread context if possible, for now direct update + // For robust applications, use a queue to pass data to the main loop + lv_label_set_text_fmt(logbox, "You:\n%s\n\nAI:\n%s", prompt, ans ? ans : "(null)"); + + free(prompt); // Free the duplicated prompt string + free(ans); + vTaskDelete(NULL); // Delete the task +} + +// UI handler: send prompt -> show reply +static void on_send(lv_event_t *e){ + const char *user = lv_textarea_get_text(ta); + if(!user || strlen(user)==0) return; + lv_label_set_text_fmt(logbox, "You:\n%s\n\nAI:\n[thinking...]", user); + + // Duplicate the user text for the task + char *prompt_copy = strdup(user); + + // Create a task to handle the Gemini call + xTaskCreate(gemini_task, "gemini_task", 8192, prompt_copy, 5, NULL); +} + +static void ui_init(void){ + // You already have BSP display/touch init in your Waveshare demos; call that first. + // Here we only do the LVGL widgets. + + // Basic container + lv_obj_t *scr = lv_scr_act(); + ta = lv_textarea_create(scr); + lv_obj_set_size(ta, 700, 120); + lv_obj_set_pos(ta, 10, 10); + lv_textarea_set_placeholder_text(ta, "Ask something…"); + + lv_obj_t *btn = lv_btn_create(scr); + lv_obj_set_size(btn, 140, 60); + lv_obj_set_pos(btn, 10, 140); + lv_obj_add_event_cb(btn, on_send, LV_EVENT_CLICKED, NULL); + lv_obj_t *lbl = lv_label_create(btn); + lv_label_set_text(lbl, "Send"); + lv_obj_center(lbl); + + logbox = lv_label_create(scr); + lv_obj_set_size(logbox, 700, 520); + lv_obj_set_pos(logbox, 10, 210); + lv_label_set_long_mode(logbox, LV_LABEL_LONG_WRAP); + lv_label_set_text(logbox, "Ready."); +} + +void app_main(void){ + // NVS (Wi-Fi stores creds; not strictly needed if Kconfig only) + ESP_ERROR_CHECK(nvs_flash_init()); + // Wi-Fi + wifi_init(); + + // Initialize display and touch + bsp_display_start(); + + // LVGL core tick + lv_init(); + const esp_timer_create_args_t tcfg = { .callback = lv_tick_cb, .name = "lv_tick" }; + esp_timer_handle_t tmr; esp_timer_create(&tcfg, &tmr); + esp_timer_start_periodic(tmr, 5000); // 5ms + + ui_init(); + + // Simple LVGL loop (replace with your panel’s event/task loop) + while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } +}