Files
p4-gemini-chatbot/p4_gemini_chat/main/main.c

202 lines
7.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 <string.h>
#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 panels event/task loop)
while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); }
}