From c1075082b3604f051d3bfc245d5823327782e84c Mon Sep 17 00:00:00 2001 From: drjones Date: Wed, 20 May 2026 10:04:24 -0700 Subject: [PATCH] chore: import local project into Gitea --- ._.gitignore | Bin 0 -> 4096 bytes .gitignore | 39 +++++ .vscode/settings.json | 7 + README.md | 24 +++ components/bsp/CMakeLists.txt | 2 + components/bsp/bsp.c | 23 +++ components/bsp/bsp.h | 12 ++ p4_gemini_chat/.vscode/settings.json | 5 + p4_gemini_chat/CMakeLists.txt | 3 + p4_gemini_chat/Kconfig.projbuild | 13 ++ p4_gemini_chat/build.bat | 2 + p4_gemini_chat/dependencies.lock | 18 +++ p4_gemini_chat/main/CMakeLists.txt | 4 + p4_gemini_chat/main/idf_component.yml | 17 +++ p4_gemini_chat/main/main.c | 201 ++++++++++++++++++++++++++ 15 files changed, 370 insertions(+) create mode 100644 ._.gitignore create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 components/bsp/CMakeLists.txt create mode 100644 components/bsp/bsp.c create mode 100644 components/bsp/bsp.h create mode 100644 p4_gemini_chat/.vscode/settings.json create mode 100644 p4_gemini_chat/CMakeLists.txt create mode 100644 p4_gemini_chat/Kconfig.projbuild create mode 100644 p4_gemini_chat/build.bat create mode 100644 p4_gemini_chat/dependencies.lock create mode 100644 p4_gemini_chat/main/CMakeLists.txt create mode 100644 p4_gemini_chat/main/idf_component.yml create mode 100644 p4_gemini_chat/main/main.c diff --git a/._.gitignore b/._.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..613fad71b9d6b2b55b94845e22a7b8a71d9db82c GIT binary patch literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103vf+zv$ zV3+~K+-O=D5#plB`MG+D1qC^&dId%KWvO|IdC92^j7$vA!g8vQxfV*HX&vQ`hQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kgR_7DJdHbEE+=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)); } +}