#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)); } }