GAMESH

游戏美术动画unity资源搬运工!

剧情党,完美控!
桐崎千棘
当前位置:首页 > 其他 > 正文内容

让小智AI接入直播间作为语音助手

admin1个月前 (10-08)其他950
  1. 在Listening状态获取消息

    • 检查当前会话是否已获取过

    • 未获取则获取新消息并调用 WakeWordInvoke()

    • 标记已获取

  2. 处理完成判断

    • 当设备回到Listening状态时,说明上一条消息已处理完成

    • 立即通知服务器完成

    • 重置标志,准备获取下一条

  3. 状态变化时重置

    • 离开Listening状态时自动重置标志

⚡ 优势

  • 不等待Idle:处理完立即回到Listening就能获取下一条

  • 响应更快:消息处理周期大大缩短

  • 逻辑简单

    • Listening状态 → 获取消息

    • Speaking/Connecting → 处理中

    • 回到Listening → 完成,获取下一条

这样消息队列处理会快很多!

#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "font_awesome_symbols.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "settings.h"

#include <wifi_station.h>
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <driver/uart.h>
#include <cstring>
#include "emote_display.h"
#include "driver/temperature_sensor.h"
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#include <queue>
#include <string>
#include <unordered_map>

#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#include <esp_lcd_ili9488.h>
#include <esp_http_client.h>
#include <esp_timer.h>

#define TAG "CompactWifiBoardLCD"
#define HTTP_NEXT_URL "http://192.168.1.90/YESHI/ai.php?action=next"
#define HTTP_COMPLETE_URL "http://192.168.1.90/YESHI/ai.php?action=complete&id=%d"
#define HTTP_GET_TIMEOUT_MS 5000
#define MESSAGE_FETCH_INTERVAL_MS 1000  // 定时器间隔:1秒

LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(font_awesome_20_4);
static const size_t LV_BUFFER_SIZE = 320 * 25;
#define USE_LVGL_DEFAULT 0

class CompactWifiBoardLCD : public WifiBoard {
private:
    Button boot_button_;
    i2c_master_bus_handle_t i2c_bus_;
    Display* display_;
    esp_timer_handle_t message_timer_ = nullptr;
    bool is_fetching_messages_ = false;
    int current_message_id_ = 0;
    bool is_processing_message_ = false;
    bool has_fetched_in_current_idle_ = false;  // 当前空闲会话是否已获取消息

    // HTTP响应数据结构
    struct HttpResponseData {
        char* buffer;
        size_t size;
        size_t capacity;
    };

    // HTTP事件处理回调
    static esp_err_t HttpEventHandler(esp_http_client_event_t *evt) {
        HttpResponseData *data = (HttpResponseData *)evt->user_data;
        
        switch(evt->event_id) {
            case HTTP_EVENT_ON_DATA:
                if (data->buffer == nullptr) {
                    data->capacity = 1024;
                    data->buffer = (char*)malloc(data->capacity);
                    data->size = 0;
                }
                
                if (data->size + evt->data_len >= data->capacity) {
                    data->capacity = data->size + evt->data_len + 1024;
                    data->buffer = (char*)realloc(data->buffer, data->capacity);
                }
                
                if (data->buffer) {
                    memcpy(data->buffer + data->size, evt->data, evt->data_len);
                    data->size += evt->data_len;
                    data->buffer[data->size] = '\0';
                }
                break;
                
            default:
                break;
        }
        return ESP_OK;
    }

    // 从服务器获取下一条消息
    bool FetchNextMessage(char* out_message, size_t max_len, int* out_message_id) {
        if (!out_message || max_len == 0 || !out_message_id) {
            return false;
        }
        
        HttpResponseData response_data = {0};
        bool success = false;
        
        esp_http_client_config_t config = {};
        config.url = HTTP_NEXT_URL;
        config.event_handler = HttpEventHandler;
        config.user_data = &response_data;
        config.timeout_ms = HTTP_GET_TIMEOUT_MS;
        
        esp_http_client_handle_t client = esp_http_client_init(&config);
        if (!client) {
            ESP_LOGE(TAG, "HTTP客户端初始化失败");
            return false;
        }
        
        esp_err_t err = esp_http_client_perform(client);
        if (err == ESP_OK) {
            int status_code = esp_http_client_get_status_code(client);
            
            if (status_code == 200 && response_data.buffer && response_data.size > 0) {
                // 解析格式: "消息ID|消息内容"
                char* separator = strchr(response_data.buffer, '|');
                if (separator) {
                    *separator = '\0';
                    *out_message_id = atoi(response_data.buffer);
                    
                    char* message_content = separator + 1;
                    size_t content_len = strlen(message_content);
                    size_t copy_len = (content_len < max_len - 1) ? content_len : max_len - 1;
                    memcpy(out_message, message_content, copy_len);
                    out_message[copy_len] = '\0';
                    
                    // 去除首尾空白字符
                    char* start = out_message;
                    while (*start && (*start == ' ' || *start == '\n' || *start == '\r' || *start == '\t')) {
                        start++;
                    }
                    if (start != out_message) {
                        memmove(out_message, start, strlen(start) + 1);
                    }
                    
                    size_t len = strlen(out_message);
                    while (len > 0 && (out_message[len-1] == ' ' || out_message[len-1] == '\n' || 
                                       out_message[len-1] == '\r' || out_message[len-1] == '\t')) {
                        out_message[--len] = '\0';
                    }
                    
                    if (strlen(out_message) > 0) {
                        // ID可以是0(随机话题)或大于0(真实消息)
                        ESP_LOGI(TAG, "收到消息 [ID=%d]: %s", *out_message_id, out_message);
                        success = true;
                    } else {
                        ESP_LOGW(TAG, "消息内容为空");
                    }
                } else {
                    ESP_LOGW(TAG, "消息格式错误,缺少分隔符");
                }
            } else if (status_code == 200) {
                ESP_LOGD(TAG, "服务器返回空响应");
            } else {
                ESP_LOGW(TAG, "HTTP状态码异常: %d", status_code);
            }
        } else {
            ESP_LOGE(TAG, "HTTP GET请求失败: %s", esp_err_to_name(err));
        }
        
        if (response_data.buffer) {
            free(response_data.buffer);
        }
        
        esp_http_client_cleanup(client);
        return success;
    }

    // 通知服务器消息处理完成
    bool NotifyMessageComplete(int message_id) {
        char complete_url[256];
        snprintf(complete_url, sizeof(complete_url), HTTP_COMPLETE_URL, message_id);
        
        esp_http_client_config_t config = {};
        config.url = complete_url;
        config.method = HTTP_METHOD_GET;
        config.timeout_ms = 3000;
        
        esp_http_client_handle_t client = esp_http_client_init(&config);
        if (!client) {
            ESP_LOGE(TAG, "HTTP客户端初始化失败");
            return false;
        }
        
        esp_err_t err = esp_http_client_perform(client);
        bool success = false;
        
        if (err == ESP_OK) {
            int status_code = esp_http_client_get_status_code(client);
            if (status_code == 200) {
                ESP_LOGI(TAG, "消息完成通知成功: ID=%d", message_id);
                success = true;
            } else {
                ESP_LOGW(TAG, "消息完成通知失败: 状态码=%d", status_code);
            }
        } else {
            ESP_LOGE(TAG, "消息完成通知请求失败: %s", esp_err_to_name(err));
        }
        
        esp_http_client_cleanup(client);
        return success;
    }

    // 定时器回调函数(借鉴Esp32LiveStream的设计)
    static void MessageTimerCallback(void* arg) {
        CompactWifiBoardLCD* self = static_cast<CompactWifiBoardLCD*>(arg);
        
        // 检查是否停止获取
        if (!self->is_fetching_messages_) {
            ESP_LOGD(TAG, "消息获取已停止,忽略定时器回调");
            return;
        }
        
        // 获取当前设备状态
        auto& app = Application::GetInstance();
        DeviceState device_state = app.GetDeviceState();
        
        ESP_LOGD(TAG, "定时器触发 - 状态: %d, 正在处理: %d, 已获取: %d", 
                 device_state, self->is_processing_message_, self->has_fetched_in_current_idle_);
        
        // 情况1:正在处理消息,检查是否回到Listening状态(说明处理完成)
        if (self->is_processing_message_) {
            // 检查是否回到Listening状态,说明上一条消息处理完成
            if (device_state == kDeviceStateListening) {
                ESP_LOGI(TAG, "回到Listening状态,消息处理完成");
                
                // 只有ID>0的消息才需要通知服务器(ID=0是随机话题)
                if (self->current_message_id_ > 0) {
                    ESP_LOGI(TAG, "通知服务器消息完成: ID=%d", self->current_message_id_);
                    if (self->NotifyMessageComplete(self->current_message_id_)) {
                        self->is_processing_message_ = false;
                        self->current_message_id_ = 0;
                        self->has_fetched_in_current_idle_ = false;
                        ESP_LOGI(TAG, "准备获取下一条消息");
                    } else {
                        ESP_LOGW(TAG, "完成通知失败,下次定时器重试");
                    }
                } else {
                    // 随机话题处理完成,无需通知服务器
                    ESP_LOGI(TAG, "随机话题处理完成,无需通知服务器");
                    self->is_processing_message_ = false;
                    self->current_message_id_ = 0;
                    self->has_fetched_in_current_idle_ = false;
                }
            }
            return;  // 正在处理中,不获取新消息
        }
        
        // 情况2:没有正在处理的消息,检查是否可以获取新消息
        // 在Listening状态下获取
        if (device_state == kDeviceStateListening) {
            // 检查当前Listening会话是否已获取过消息
            if (!self->has_fetched_in_current_idle_) {
                ESP_LOGI(TAG, "设备处于Listening状态且未获取过消息,尝试获取新消息");
                
                char message_buffer[512];
                int fetched_message_id = 0;
                
                if (self->FetchNextMessage(message_buffer, sizeof(message_buffer), &fetched_message_id)) {
                    if (fetched_message_id == 0) {
                        ESP_LOGI(TAG, "获取到随机话题: %s", message_buffer);
                    } else {
                        ESP_LOGI(TAG, "获取到消息 [ID=%d]: %s", fetched_message_id, message_buffer);
                    }
                    
                    // 标记正在处理
                    self->is_processing_message_ = true;
                    self->current_message_id_ = fetched_message_id;
                    self->has_fetched_in_current_idle_ = true;  // 标记当前会话已获取
                    
                    // 根据设备状态选择处理方式
                    if (device_state == kDeviceStateListening) {
                        ESP_LOGI(TAG, "设备处于Listening状态,使用 SendWakeWordDetected 处理");
                        Protocol* protocol = &Application::GetInstance().GetProtocol();
                        protocol->SendWakeWordDetected(message_buffer);
                    } else {
                        ESP_LOGI(TAG, "设备不在Listening状态,使用 WakeWordInvoke 处理");
                        app.WakeWordInvoke(message_buffer);
                    }
                } else {
                    ESP_LOGD(TAG, "获取消息失败,等待下次定时器");
                }
            } else {
                ESP_LOGD(TAG, "当前Listening会话已获取过消息,等待状态变化");
            }
        } else {
            // 不在Listening状态,重置获取标志
            if (self->has_fetched_in_current_idle_ && device_state != kDeviceStateListening) {
                ESP_LOGD(TAG, "设备离开Listening状态,重置获取标志");
                self->has_fetched_in_current_idle_ = false;
            }
        }
    }

    // 启动消息获取定时器
    bool StartMessageFetching() {
        if (is_fetching_messages_) {
            ESP_LOGW(TAG, "消息获取已在运行");
            return true;
        }
        
        ESP_LOGI(TAG, "启动消息获取定时器...");
        
        // 创建定时器配置
        esp_timer_create_args_t timer_args = {
            .callback = MessageTimerCallback,
            .arg = this,
            .dispatch_method = ESP_TIMER_TASK,
            .name = "message_fetch_timer"
        };
        
        // 创建定时器
        esp_err_t ret = esp_timer_create(&timer_args, &message_timer_);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "创建定时器失败: %s", esp_err_to_name(ret));
            return false;
        }
        
        // 启动定时器(每秒触发一次)
        ret = esp_timer_start_periodic(message_timer_, MESSAGE_FETCH_INTERVAL_MS * 1000);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "启动定时器失败: %s", esp_err_to_name(ret));
            esp_timer_delete(message_timer_);
            message_timer_ = nullptr;
            return false;
        }
        
        is_fetching_messages_ = true;
        has_fetched_in_current_idle_ = false;
        ESP_LOGI(TAG, "消息获取定时器启动成功,间隔: %d ms", MESSAGE_FETCH_INTERVAL_MS);
        return true;
    }

    // 停止消息获取
    void StopMessageFetching() {
        if (!is_fetching_messages_) {
            return;
        }
        
        ESP_LOGI(TAG, "停止消息获取定时器");
        is_fetching_messages_ = false;
        
        if (message_timer_) {
            esp_timer_stop(message_timer_);
            esp_timer_delete(message_timer_);
            message_timer_ = nullptr;
        }
        
        has_fetched_in_current_idle_ = false;
        ESP_LOGI(TAG, "消息获取定时器已停止");
    }

    void InitializeSpi() {
        spi_bus_config_t buscfg = {};
        buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
        buscfg.miso_io_num = GPIO_NUM_NC;
        buscfg.sclk_io_num = DISPLAY_CLK_PIN;
        buscfg.quadwp_io_num = GPIO_NUM_NC;
        buscfg.quadhd_io_num = GPIO_NUM_NC;
        buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
        ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
    }

    void InitializeButtons() {
        boot_button_.OnClick([this]() {
            auto& app = Application::GetInstance();
            if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
                ResetWifiConfiguration();
            }
            app.ToggleChatState();
        });
    }

    void InitializeDisplay() {
        esp_lcd_panel_io_handle_t panel_io = nullptr;
        esp_lcd_panel_handle_t panel = nullptr;
        
        ESP_LOGD(TAG, "Install panel IO");
        esp_lcd_panel_io_spi_config_t io_config = {};
        io_config.cs_gpio_num = DISPLAY_CS_PIN;
        io_config.dc_gpio_num = DISPLAY_DC_PIN;
        io_config.spi_mode = DISPLAY_SPI_MODE;
        io_config.pclk_hz = 40 * 1000 * 1000;
        io_config.trans_queue_depth = 10;
        io_config.lcd_cmd_bits = 8;
        io_config.lcd_param_bits = 8;
        ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));

        ESP_LOGD(TAG, "Install LCD driver");
        esp_lcd_panel_dev_config_t panel_config = {};
        panel_config.reset_gpio_num = DISPLAY_RST_PIN;
        panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
        panel_config.bits_per_pixel = 18;
        ESP_ERROR_CHECK(esp_lcd_new_panel_ili9488(panel_io, &panel_config, LV_BUFFER_SIZE, &panel));
        
        esp_lcd_panel_reset(panel);
        esp_lcd_panel_init(panel);
        esp_lcd_panel_invert_color(panel, false);
        esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
        esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
        esp_lcd_panel_disp_on_off(panel, true);
        
#if USE_LVGL_DEFAULT
        display_ = new SpiLcdDisplay(panel_io, panel,
                                    DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, 
                                    DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
                                    {
                                        .text_font = &font_puhui_20_4,
                                        .icon_font = &font_awesome_20_4,
                                        .emoji_font = font_emoji_64_init(),
                                    });
#else
        display_ = new anim::EmoteDisplay(panel, panel_io);
#endif
    }

public:
    CompactWifiBoardLCD() : boot_button_(BOOT_BUTTON_GPIO) {
        InitializeSpi();
        InitializeDisplay();
        InitializeButtons();

        if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
            GetBacklight()->RestoreBrightness();
        }

        // 延迟启动消息获取定时器
        xTaskCreate([](void* param) {
            CompactWifiBoardLCD* self = (CompactWifiBoardLCD*)param;
            
            // 等待WiFi稳定连接(最多30秒)
            int wifi_wait_count = 0;
            while (!WifiStation::GetInstance().IsConnected() && wifi_wait_count < 30) {
                vTaskDelay(pdMS_TO_TICKS(1000));
                wifi_wait_count++;
            }
            
            if (WifiStation::GetInstance().IsConnected()) {
                // 再等待3秒确保系统稳定
                vTaskDelay(pdMS_TO_TICKS(3000));
                self->StartMessageFetching();
            } else {
                ESP_LOGW(TAG, "WiFi未连接,跳过消息获取启动");
            }
            
            vTaskDelete(NULL);
        }, "msg_init", 4096, this, 1, NULL);
    }

    virtual ~CompactWifiBoardLCD() {
        StopMessageFetching();
    }

    virtual AudioCodec* GetAudioCodec() override {
        static NoAudioCodecSimplex audio_codec(
            AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
            AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_RIGHT, 
            AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_LEFT);
        return &audio_codec;
    }

    virtual Display* GetDisplay() override {
        return display_;
    }

    virtual Backlight* GetBacklight() override {
        if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
            static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
            return &backlight;
        }
        return nullptr;
    }
};

DECLARE_BOARD(CompactWifiBoardLCD);
<?php
// 基础配置
header('Content-Type: application/json; charset=utf-8');
header('X-XSS-Protection: 1; mode=block');

// 数据库配置
define('DB_FILE', __DIR__ . '/ai_messages.db');

// 工具函数:获取客户端IP
function getClientIP() {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '未知IP';
    return trim(explode(',', $ip)[0]);
}

// 工具函数:输入清洗
function cleanStr($str) {
    if (!is_string($str)) $str = (string)$str;
    $str = trim($str);
    $str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
    return $str;
}

// 工具函数:格式化消息为嵌入式支持的字符串
function formatToDeviceStr($messageData) {
    $username = cleanStr($messageData['username'] ?? '未知用户');
    $content = cleanStr($messageData['content'] ?? '');
    $msgType = cleanStr($messageData['type'] ?? 'comment');
    
    switch ($msgType) {
        case 'gift':
            $giftName = cleanStr($messageData['giftName'] ?? '礼物');
            $giftCount = is_numeric($messageData['giftCount'] ?? 1) ? (int)$messageData['giftCount'] : 1;
            $countStr = $giftCount > 1 ? "{$giftCount}个" : "1个";
            return "{$username}送给主播{$countStr}{$giftName}";
        
        case 'enter':
            return "{$username}来了";
        
        case 'like':
            return "{$username}给主播点赞";
        
        case 'comment':
        default:
            return "{$username}说:{$content}";
    }
}

// 初始化数据库
function initDatabase() {
    $db = new SQLite3(DB_FILE);
    
    // 创建消息队列表
    $db->exec("
        CREATE TABLE IF NOT EXISTS message_queue (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            message_text TEXT NOT NULL,
            message_type VARCHAR(20) DEFAULT 'comment',
            username VARCHAR(100),
            raw_data TEXT,
            client_ip VARCHAR(50),
            status VARCHAR(20) DEFAULT 'pending',
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            processing_at DATETIME,
            processed_at DATETIME
        )
    ");
    
    // 创建索引提升查询性能
    $db->exec("CREATE INDEX IF NOT EXISTS idx_status ON message_queue(status)");
    $db->exec("CREATE INDEX IF NOT EXISTS idx_created_at ON message_queue(created_at)");
    
    $db->close();
}

// 添加消息到队列
function addMessageToQueue($messageData, $deviceStr, $clientIP) {
    $db = new SQLite3(DB_FILE);
    
    $stmt = $db->prepare("
        INSERT INTO message_queue (message_text, message_type, username, raw_data, client_ip, status)
        VALUES (:message_text, :message_type, :username, :raw_data, :client_ip, 'pending')
    ");
    
    $stmt->bindValue(':message_text', $deviceStr, SQLITE3_TEXT);
    $stmt->bindValue(':message_type', $messageData['type'] ?? 'comment', SQLITE3_TEXT);
    $stmt->bindValue(':username', $messageData['username'] ?? '未知用户', SQLITE3_TEXT);
    $stmt->bindValue(':raw_data', json_encode($messageData, JSON_UNESCAPED_UNICODE), SQLITE3_TEXT);
    $stmt->bindValue(':client_ip', $clientIP, SQLITE3_TEXT);
    
    $result = $stmt->execute();
    $insertId = $db->lastInsertRowID();
    
    $db->close();
    
    return $insertId;
}

// 获取下一条待处理消息
function getNextMessage() {
    $db = new SQLite3(DB_FILE);
    
    // 查询最早的待处理消息
    $stmt = $db->prepare("
        SELECT id, message_text, message_type, username, created_at
        FROM message_queue
        WHERE status = 'pending'
        ORDER BY created_at ASC
        LIMIT 1
    ");
    
    $result = $stmt->execute();
    $message = $result->fetchArray(SQLITE3_ASSOC);
    
    // 如果找到消息,标记为处理中
    if ($message) {
        $updateStmt = $db->prepare("
            UPDATE message_queue
            SET status = 'processing', processing_at = CURRENT_TIMESTAMP
            WHERE id = :id
        ");
        $updateStmt->bindValue(':id', $message['id'], SQLITE3_INTEGER);
        $updateStmt->execute();
    }
    
    $db->close();
    
    return $message;
}

// 获取随机话题
function getRandomTopic() {
    $topics = [
        "今天天气真不错呀",
        "大家有什么想聊的吗",
        "有没有人在看直播呢",
        "来个朋友陪我聊聊天吧",
        "好无聊啊,有人吗",
        "今天直播间好安静呀",
        "大家都在忙什么呢",
        "有没有新朋友来玩呀",
        "老朋友们在哪里",
        "来点互动好不好",
        "发个666让我看看你们",
        "有人想听我唱歌吗",
        "今天想和大家分享点什么",
        "直播间的氛围好像有点冷清",
        "来来来,活跃一下气氛",
        "有什么想问我的吗",
        "今天过得怎么样呀",
        "周末有什么计划吗",
        "最近有什么好玩的事情",
        "给大家讲个笑话好不好"
    ];
    
    $randomIndex = array_rand($topics);
    return $topics[$randomIndex];
}

// 标记消息为已完成并删除
function markMessageComplete($messageId) {
    $db = new SQLite3(DB_FILE);
    
    // 先标记为已完成(可选,用于统计)
    $stmt = $db->prepare("
        UPDATE message_queue
        SET status = 'completed', processed_at = CURRENT_TIMESTAMP
        WHERE id = :id
    ");
    $stmt->bindValue(':id', $messageId, SQLITE3_INTEGER);
    $stmt->execute();
    
    // 删除已完成的消息
    $deleteStmt = $db->prepare("DELETE FROM message_queue WHERE id = :id");
    $deleteStmt->bindValue(':id', $messageId, SQLITE3_INTEGER);
    $deleteStmt->execute();
    
    $db->close();
    
    return true;
}

// 获取队列统计信息
function getQueueStats() {
    $db = new SQLite3(DB_FILE);
    
    $result = $db->query("
        SELECT 
            COUNT(*) as total,
            SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
            SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END) as processing
        FROM message_queue
    ");
    
    $stats = $result->fetchArray(SQLITE3_ASSOC);
    $db->close();
    
    return $stats;
}

// 清理长时间卡住的处理中消息(防止死锁)
function cleanupStuckMessages($timeoutMinutes = 5) {
    $db = new SQLite3(DB_FILE);
    
    $stmt = $db->prepare("
        UPDATE message_queue
        SET status = 'pending', processing_at = NULL
        WHERE status = 'processing'
        AND datetime(processing_at, '+' || :timeout || ' minutes') < datetime('now')
    ");
    $stmt->bindValue(':timeout', $timeoutMinutes, SQLITE3_INTEGER);
    $stmt->execute();
    
    $affected = $db->changes();
    $db->close();
    
    return $affected;
}

// ---------------------- 主逻辑 ----------------------

// 初始化数据库
initDatabase();

$clientIP = getClientIP();
$action = $_GET['action'] ?? '';

// 处理不同的action
switch ($action) {
    case 'add':
        // 添加新消息到队列(POST请求)
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            http_response_code(405);
            echo json_encode(['status' => 'error', 'message' => '仅支持POST请求']);
            exit;
        }
        
        $rawPost = file_get_contents('php://input');
        if (empty($rawPost)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => 'POST数据为空']);
            exit;
        }
        
        $postData = json_decode($rawPost, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $postData = ['type' => 'comment', 'content' => $rawPost, 'username' => '未知用户'];
        }
        
        $deviceStr = formatToDeviceStr($postData);
        $insertId = addMessageToQueue($postData, $deviceStr, $clientIP);
        
        $stats = getQueueStats();
        
        echo json_encode([
            'status' => 'success',
            'message' => '消息已加入队列',
            'data' => [
                'queue_id' => $insertId,
                'message' => $deviceStr,
                'queue_stats' => $stats
            ]
        ]);
        break;
    
    case 'next':
        // AI设备请求下一条消息(GET请求)
        cleanupStuckMessages(5); // 清理超过5分钟未完成的消息
        
        $message = getNextMessage();
        
        if ($message) {
            // 返回格式:消息ID|消息内容
            // 这样ESP32可以解析出ID用于后续完成通知
            header('Content-Type: text/plain; charset=utf-8');
            echo $message['id'] . '|' . $message['message_text'];
        } else {
            // 队列为空,返回随机话题
            $randomTopic = getRandomTopic();
            
            // 使用特殊ID=0表示这是随机话题,不需要完成通知
            header('Content-Type: text/plain; charset=utf-8');
            echo '0|' . $randomTopic;
            
            // 记录日志(可选)
            error_log("[AI Queue] 队列为空,返回随机话题: " . $randomTopic);
        }
        break;
    
    case 'complete':
        // AI设备完成处理,删除消息(POST或GET请求)
        $messageId = $_GET['id'] ?? $_POST['id'] ?? null;
        
        if (!$messageId || !is_numeric($messageId)) {
            http_response_code(400);
            echo json_encode(['status' => 'error', 'message' => '缺少消息ID']);
            exit;
        }
        
        markMessageComplete((int)$messageId);
        
        $stats = getQueueStats();
        
        echo json_encode([
            'status' => 'success',
            'message' => '消息已标记完成并删除',
            'data' => [
                'completed_id' => (int)$messageId,
                'queue_stats' => $stats
            ]
        ]);
        break;
    
    case 'stats':
        // 获取队列统计信息
        $stats = getQueueStats();
        
        echo json_encode([
            'status' => 'success',
            'data' => $stats
        ]);
        break;
    
    case 'clear':
        // 清空队列(危险操作,可选)
        $db = new SQLite3(DB_FILE);
        $db->exec("DELETE FROM message_queue");
        $affected = $db->changes();
        $db->close();
        
        echo json_encode([
            'status' => 'success',
            'message' => "已清空队列,删除 {$affected} 条消息"
        ]);
        break;
    
    default:
        http_response_code(400);
        echo json_encode([
            'status' => 'error',
            'message' => '无效的action参数',
            'available_actions' => [
                'add' => '添加消息到队列(POST)',
                'next' => 'AI获取下一条消息(GET)',
                'complete' => '标记消息完成(GET/POST,需要id参数)',
                'stats' => '获取队列统计(GET)',
                'clear' => '清空队列(GET)'
            ]
        ]);
}
?>


扫描二维码推送至手机访问。

本サイト上に掲載の文章、画像、写真などを無断で複製することは法律で禁じられています。全ての著作権はGAMESHに帰属します。

本文链接:https://pylblog.com/post/265.html

分享给朋友:

相关文章

NEWIFI入手

NEWIFI入手

作为一款智能路由器NEWIFIMINI可谓是十分大众化的,性价比非常高!产品名称:新路由NewifiMini (新路由Y1) 产品型号:R6830 产品尺寸:130.2mm*120.8mm...

尼尔 机械军团

尼尔 机械军团

记得首次看到这个游戏是在去年E3,当时这款游戏就给我留下了很深的印象,今年e3它再次亮相并且再一次成功博得我的眼球!目前公布了一段新的预告,伴随着粉丝们熟悉的异域风情原声,视频展示了游戏中各色各样的场...

《最终幻想》制作组访谈

《最终幻想》制作组访谈

随着CG电影科技的飞速发展,毫无疑问今年的最具视觉冲击力的电影大片非《最终幻想》莫属,片中的角色和环境的真实度之高,让包括Tom Hanks在内的一大批超级明星都感到了失业的危机。 本片由曾...

关于首页弹幕插件逆天

关于首页弹幕插件逆天

最近很多人反映我这个首页弹幕弹出过多,影响浏览!其实这个是我从GitHub上面搞来的,这个插件有个bug就是当用户离开页面时弹幕会停止前进,一直停留在起始位置,当用户再次回来时就会弹出大量弹幕,影响用...

UTAU版FC《勇者斗恶龙1、2、3》经典BGM合集

UTAU版FC《勇者斗恶龙1、2、3》经典BGM合集

RPG鼻祖勇者斗恶龙被日本誉为国民RPG可见DQ在日本的地位,不过在中国感觉FF人气似乎比DQ要高,虽然我也是先了解FF,但是DQ仍然是我比较喜欢的系列,我通关的第一部DQ作品要比FF作品早好几年!所...

LEDE编译出错recipe for target 'install' failed

LEDE编译出错recipe for target 'install' failed

编译LEDE出现错误:/home/gamesh/Music/lede-20221001/include/image.mk:336: recipe for target '/home/games...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。