1362 lines
40 KiB
C
1362 lines
40 KiB
C
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include "lisa_ui.h"
|
||
#include "lisa_ui_nav_scr.h"
|
||
#include "lisa_ui_nav_scr_ids.h"
|
||
#include "lisa_ui_anim_ext.h"
|
||
#include "lisa_ui_assets.h"
|
||
#include "lisa_ui_toast.h"
|
||
|
||
#include "model_voice.h"
|
||
#include "model_alarm.h"
|
||
#include "lisa_ui_llm_primary.h"
|
||
#include "emoji_anim.h"
|
||
#include "model_camera.h"
|
||
#include "model_wifi.h"
|
||
#include "model_battery.h"
|
||
#include "voice_cloud.h"
|
||
#include "display/lv_img_net_loader.h"
|
||
#ifdef CONFIG_BATTERY_COLLECTION
|
||
#include "battery/battery.h"
|
||
#endif
|
||
|
||
#define WORK_TYPE_VOICE 0
|
||
#define WORK_TYPE_IMG_REC 1
|
||
|
||
struct home_nav_scr_data {
|
||
lv_obj_t *view;
|
||
lv_timer_t *anim_timer;
|
||
lv_timer_t *wifi_status_timer;
|
||
lv_timer_t *camera_capture_timer;
|
||
lv_timer_t *img_hide_timer;
|
||
lv_timer_t *standby_text_timer;
|
||
lv_timer_t *finish_check_timer;
|
||
uint32_t standby_text_index;
|
||
lv_img_dsc_t img;
|
||
lv_img_dsc_t *net_img;
|
||
uint8_t *cap_buf;
|
||
uint32_t cap_buf_size;
|
||
uint8_t img_rec_running;
|
||
uint8_t img_rec_in_progress;
|
||
uint8_t img_rec_triggered;
|
||
uint8_t img_rec_is_mcp;
|
||
uint8_t img_rec_is_button;
|
||
uint8_t mcp_emoji_running;
|
||
uint8_t mcp_loading;
|
||
uint8_t finished;
|
||
uint8_t speaking;
|
||
uint8_t work_type;
|
||
};
|
||
|
||
static void img_hide_timer_cb(lv_timer_t *timer);
|
||
static void hide_img_now(struct home_nav_scr_data *scr_data);
|
||
|
||
static void show_emoji_anim(struct home_nav_scr_data *d, const char *emoji_name, uint8_t imm);
|
||
static void model_voice_on_emoji(void *arg, const char *name);
|
||
static void model_voice_on_mcp_emoji(void *arg, const char *name);
|
||
static void model_voice_on_mcp_loading(void *arg, bool is_loading, const char *loading_text);
|
||
static void model_voice_on_start(void *arg);
|
||
static void model_voice_on_finished(void *arg);
|
||
static void model_voice_on_tts_text_start(void *arg);
|
||
static void model_voice_on_tts_text_end(void *arg);
|
||
static void model_voice_on_tts_text_update(const char *text, void *arg);
|
||
static void model_voice_on_iat_text_start(void *arg);
|
||
static void model_voice_on_iat_text_end(void *arg);
|
||
static void model_voice_on_iat_text_update(const char *text, void *arg);
|
||
static void model_voice_on_connected(void *arg);
|
||
static void model_voice_on_disconnected(void *arg);
|
||
static void model_voice_on_tts_player_playing(void *arg);
|
||
static void model_voice_on_tts_player_stoped(void *arg);
|
||
static void model_voice_on_image_rec(void *arg);
|
||
static void model_voice_on_info_show(void *arg);
|
||
static void model_voice_on_image_preview(void *arg);
|
||
static void model_voice_on_image_url(void *arg, const char *url);
|
||
static void model_voice_on_standby_text_update(void *arg, const char *text, bool is_cloud_text);
|
||
static void model_voice_on_show_qrcode(void *arg);
|
||
static void model_voice_on_standby_texts_changed(void *arg);
|
||
static void model_voice_on_wakeup_mode_changed(void *arg, model_voice_wakeup_mode_t mode);
|
||
static void standby_text_timer_cb(lv_timer_t *timer);
|
||
static void standby_text_timer_update(struct home_nav_scr_data *scr_data);
|
||
#ifdef CONFIG_OTA
|
||
static void model_voice_on_ota_state_change(const ota_state_t *state, void *arg);
|
||
#endif
|
||
static void home_update_full_duplex_icon(struct home_nav_scr_data *scr_data);
|
||
static void home_update_alarm_icon(struct home_nav_scr_data *scr_data);
|
||
static void home_update_battery_icon(struct home_nav_scr_data *scr_data);
|
||
#ifdef CONFIG_BATTERY_COLLECTION
|
||
static void home_update_battery_icon_from_hw(struct home_nav_scr_data *scr_data);
|
||
#endif
|
||
|
||
static int rotate_rgb565_cw90_inplace(uint8_t *rgb565_buf, uint16_t width, uint16_t height)
|
||
{
|
||
if (!rgb565_buf || width == 0 || height == 0) {
|
||
return -1;
|
||
}
|
||
|
||
uint32_t pixel_count = (uint32_t)width * (uint32_t)height;
|
||
uint32_t buffer_size = pixel_count * sizeof(uint16_t);
|
||
uint16_t *src = (uint16_t *)rgb565_buf;
|
||
uint16_t *rotated = lisa_ui_malloc(buffer_size);
|
||
|
||
if (!rotated) {
|
||
return -2;
|
||
}
|
||
|
||
for (uint32_t y = 0; y < height; y++) {
|
||
for (uint32_t x = 0; x < width; x++) {
|
||
uint32_t dst_x = (uint32_t)height - 1 - y;
|
||
uint32_t dst_y = x;
|
||
rotated[dst_y * height + dst_x] = src[y * width + x];
|
||
}
|
||
}
|
||
|
||
memcpy(rgb565_buf, rotated, buffer_size);
|
||
lisa_ui_free(rotated);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static const void *battery_power_icons[] = {
|
||
&icons_ic_status_power0_png,
|
||
&icons_ic_status_power1_png,
|
||
&icons_ic_status_power2_png,
|
||
&icons_ic_status_power3_png,
|
||
&icons_ic_status_power4_png,
|
||
&icons_ic_status_power5_png,
|
||
&icons_ic_status_power6_png,
|
||
&icons_ic_status_power7_png,
|
||
&icons_ic_status_power8_png,
|
||
&icons_ic_status_power9_png,
|
||
};
|
||
|
||
static const void *battery_charging_icons[] = {
|
||
&icons_ic_status_charging0_png,
|
||
&icons_ic_status_charging1_png,
|
||
&icons_ic_status_charging2_png,
|
||
&icons_ic_status_charging3_png,
|
||
&icons_ic_status_charging4_png,
|
||
&icons_ic_status_charging5_png,
|
||
&icons_ic_status_charging6_png,
|
||
&icons_ic_status_charging7_png,
|
||
&icons_ic_status_charging8_png,
|
||
&icons_ic_status_charging9_png,
|
||
};
|
||
|
||
const struct model_voice_cb model_voice_cbs = {
|
||
.on_tts_stoped = model_voice_on_tts_player_stoped,
|
||
.on_tts_playing = model_voice_on_tts_player_playing,
|
||
.on_emoji = model_voice_on_emoji,
|
||
.on_mcp_emoji = model_voice_on_mcp_emoji,
|
||
.on_mcp_loading = model_voice_on_mcp_loading,
|
||
.on_connected = model_voice_on_connected,
|
||
.on_disconnected = model_voice_on_disconnected,
|
||
.on_start = model_voice_on_start,
|
||
.on_finished = model_voice_on_finished,
|
||
.on_tts_text_start = model_voice_on_tts_text_start,
|
||
.on_tts_text_update = model_voice_on_tts_text_update,
|
||
.on_tts_text_end = model_voice_on_tts_text_end,
|
||
.on_iat_text_start = model_voice_on_iat_text_start,
|
||
.on_iat_text_update = model_voice_on_iat_text_update,
|
||
.on_iat_text_end = model_voice_on_iat_text_end,
|
||
.on_image_rec = model_voice_on_image_rec,
|
||
.on_info_show = model_voice_on_info_show,
|
||
.on_image_preview = model_voice_on_image_preview,
|
||
.on_image_url = model_voice_on_image_url,
|
||
.on_standby_texts_changed = model_voice_on_standby_texts_changed,
|
||
.on_standby_text_update = model_voice_on_standby_text_update,
|
||
.on_show_qrcode = model_voice_on_show_qrcode,
|
||
.on_wakeup_mode_changed = model_voice_on_wakeup_mode_changed,
|
||
#ifdef CONFIG_OTA
|
||
.on_ota_state_change = model_voice_on_ota_state_change,
|
||
#endif
|
||
};
|
||
|
||
static void home_update_full_duplex_icon(struct home_nav_scr_data *scr_data)
|
||
{
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
model_voice_wakeup_mode_t mode = model_voice_wakeup_mode_get();
|
||
bool is_full_duplex = (mode == MODEL_VOICE_WAKEUP_MODE_VOICE_MULTI);
|
||
lisa_ui_llm_primary_set_full_duplex_icon_visible(scr_data->view, is_full_duplex);
|
||
}
|
||
|
||
static void home_update_alarm_icon(struct home_nav_scr_data *scr_data)
|
||
{
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
uint32_t count = model_alarm_count_get();
|
||
lisa_ui_llm_primary_set_alarm_icon_visible(scr_data->view, count > 0);
|
||
}
|
||
|
||
static const void *home_select_battery_icon(const model_battery_info_t *info)
|
||
{
|
||
if (!info) {
|
||
return NULL;
|
||
}
|
||
|
||
if (info->status == MODEL_BATTERY_STATUS_UNKNOWN ||
|
||
info->status == MODEL_BATTERY_STATUS_NO_BATTERY) {
|
||
return NULL;
|
||
}
|
||
|
||
uint8_t level = info->level;
|
||
uint8_t idx = level / 10;
|
||
if (idx > 9) {
|
||
idx = 9;
|
||
}
|
||
|
||
if (info->status == MODEL_BATTERY_STATUS_CHARGING ||
|
||
info->status == MODEL_BATTERY_STATUS_CHARGE_DONE) {
|
||
return battery_charging_icons[idx];
|
||
}
|
||
|
||
return battery_power_icons[idx];
|
||
}
|
||
|
||
#ifdef CONFIG_BATTERY_COLLECTION
|
||
static model_battery_status_t home_convert_hw_battery_status(battery_status_t status)
|
||
{
|
||
switch (status) {
|
||
case BATTERY_STATUS_NO_BATTERY:
|
||
return MODEL_BATTERY_STATUS_NO_BATTERY;
|
||
case BATTERY_STATUS_NOT_CONNECT:
|
||
return MODEL_BATTERY_STATUS_NOT_CONNECT;
|
||
case BATTERY_STATUS_CHARGING:
|
||
return MODEL_BATTERY_STATUS_CHARGING;
|
||
case BATTERY_STATUS_CHARGE_DONE:
|
||
return MODEL_BATTERY_STATUS_CHARGE_DONE;
|
||
case BATTERY_STATUS_UNKNOWN:
|
||
default:
|
||
return MODEL_BATTERY_STATUS_UNKNOWN;
|
||
}
|
||
}
|
||
|
||
static void home_update_battery_icon_from_hw(struct home_nav_scr_data *scr_data)
|
||
{
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
model_battery_info_t info = {
|
||
.level = battery_get_pct(),
|
||
.status = home_convert_hw_battery_status(battery_get_status()),
|
||
};
|
||
|
||
const void *icon = home_select_battery_icon(&info);
|
||
lisa_ui_llm_primary_set_battery_img(scr_data->view, icon);
|
||
}
|
||
#endif
|
||
|
||
static void home_update_battery_icon(struct home_nav_scr_data *scr_data)
|
||
{
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
model_battery_info_t info;
|
||
if (model_battery_get_info(&info) != 0) {
|
||
return;
|
||
}
|
||
|
||
#ifdef CONFIG_BATTERY_COLLECTION
|
||
if (info.status == MODEL_BATTERY_STATUS_UNKNOWN) {
|
||
home_update_battery_icon_from_hw(scr_data);
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
const void *icon = home_select_battery_icon(&info);
|
||
lisa_ui_llm_primary_set_battery_img(scr_data->view, icon);
|
||
}
|
||
|
||
static void model_battery_on_battery_status_update(const model_battery_info_t *info, void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
const void *icon = home_select_battery_icon(info);
|
||
lisa_ui_llm_primary_set_battery_img(scr_data->view, icon);
|
||
}
|
||
|
||
static void standby_text_timer_update(struct home_nav_scr_data *scr_data)
|
||
{
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
uint32_t count = model_voice_standby_text_count_get();
|
||
uint32_t interval_ms = model_voice_standby_text_interval_ms_get();
|
||
|
||
bool allow_play = true;
|
||
if (scr_data->img_rec_running || scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
allow_play = false;
|
||
}
|
||
if (model_voice_cloud_is_running()) {
|
||
allow_play = false;
|
||
}
|
||
if (model_voice_tts_is_playing()) {
|
||
allow_play = false;
|
||
}
|
||
|
||
if (!allow_play || count == 0 || interval_ms == 0) {
|
||
if (scr_data->standby_text_timer) {
|
||
lv_timer_pause(scr_data->standby_text_timer);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!scr_data->standby_text_timer) {
|
||
scr_data->standby_text_timer = lv_timer_create(standby_text_timer_cb, interval_ms, scr_data);
|
||
if (!scr_data->standby_text_timer) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
lv_timer_set_period(scr_data->standby_text_timer, interval_ms);
|
||
lv_timer_resume(scr_data->standby_text_timer);
|
||
}
|
||
|
||
static void standby_text_timer_cb(lv_timer_t *timer)
|
||
{
|
||
struct home_nav_scr_data *scr_data = timer ? timer->user_data : NULL;
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
standby_text_timer_update(scr_data);
|
||
|
||
uint32_t count = model_voice_standby_text_count_get();
|
||
if (count == 0) {
|
||
return;
|
||
}
|
||
|
||
scr_data->standby_text_index = (scr_data->standby_text_index + 1) % count;
|
||
const char *text = model_voice_standby_text_get(scr_data->standby_text_index);
|
||
if (text) {
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, text);
|
||
}
|
||
}
|
||
|
||
static void model_voice_on_standby_texts_changed(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
scr_data->standby_text_index = 0;
|
||
standby_text_timer_update(scr_data);
|
||
|
||
if (scr_data->img_rec_running || scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
return;
|
||
}
|
||
if (model_voice_cloud_is_running()) {
|
||
return;
|
||
}
|
||
if (model_voice_tts_is_playing()) {
|
||
return;
|
||
}
|
||
|
||
const char *text = model_voice_standby_text_get(0);
|
||
if (text) {
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, text);
|
||
}
|
||
}
|
||
|
||
static void camera_capture_timer_callback(lv_timer_t *timer)
|
||
{
|
||
if (!timer || !timer->user_data) {
|
||
return;
|
||
}
|
||
|
||
struct home_nav_scr_data *d = timer->user_data;
|
||
uint16_t width = 0;
|
||
uint16_t height = 0;
|
||
uint32_t image_size = 0;
|
||
int ret = 0;
|
||
|
||
if (!d->view) {
|
||
lv_timer_pause(timer);
|
||
return;
|
||
}
|
||
|
||
/* 如果识别已触发,停止预览拍照 */
|
||
if (!d->img_rec_running) {
|
||
lv_timer_pause(timer);
|
||
return;
|
||
}
|
||
|
||
d->img.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||
d->img.header.always_zero = 0;
|
||
d->img.header.reserved = 0;
|
||
|
||
ret = model_camera_get_framesize(&width, &height);
|
||
if (ret != 0 || width == 0 || height == 0) {
|
||
LISA_UI_LOGE("Get camera frame size failed: ret=%d, w=%u, h=%u", ret, width, height);
|
||
d->img_rec_running = false;
|
||
d->img_rec_triggered = false;
|
||
lv_timer_pause(timer);
|
||
return;
|
||
}
|
||
|
||
image_size = (uint32_t)width * (uint32_t)height * 2U;
|
||
if (image_size == 0) {
|
||
LISA_UI_LOGE("Invalid image size, w=%u, h=%u", width, height);
|
||
d->img_rec_running = false;
|
||
d->img_rec_triggered = false;
|
||
lv_timer_pause(timer);
|
||
return;
|
||
}
|
||
|
||
if (d->cap_buf == NULL || d->cap_buf_size < image_size) {
|
||
if (d->cap_buf != NULL) {
|
||
lisa_ui_free(d->cap_buf);
|
||
d->cap_buf = NULL;
|
||
d->cap_buf_size = 0;
|
||
}
|
||
|
||
d->cap_buf = lisa_ui_malloc(image_size);
|
||
if (!d->cap_buf) {
|
||
LISA_UI_LOGE("Failed to alloc capture buffer: %u", image_size);
|
||
d->img_rec_running = false;
|
||
d->img_rec_triggered = false;
|
||
lv_timer_pause(timer);
|
||
return;
|
||
}
|
||
d->cap_buf_size = image_size;
|
||
}
|
||
|
||
ret = model_camera_capture(d->cap_buf, image_size);
|
||
if (ret != 0) {
|
||
LISA_UI_LOGE("Camera capture failed: %d", ret);
|
||
d->img_rec_running = false;
|
||
d->img_rec_triggered = false;
|
||
lv_timer_pause(timer);
|
||
return;
|
||
}
|
||
|
||
d->img.data = d->cap_buf;
|
||
d->img.data_size = image_size;
|
||
d->img.header.w = width;
|
||
d->img.header.h = height;
|
||
|
||
if (!d->img_rec_is_button) {
|
||
lisa_ui_llm_primary_img_show(d->view, &d->img);
|
||
}
|
||
}
|
||
|
||
static void img_hide_timer_cb(lv_timer_t *timer)
|
||
{
|
||
struct home_nav_scr_data *scr_data = timer->user_data;
|
||
|
||
if (scr_data) {
|
||
hide_img_now(scr_data);
|
||
}
|
||
|
||
lv_timer_del(timer);
|
||
|
||
scr_data->img_hide_timer = NULL;
|
||
}
|
||
|
||
static void hide_img_now(struct home_nav_scr_data *scr_data)
|
||
{
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
lisa_ui_llm_primary_img_hide(scr_data->view);
|
||
if (scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
scr_data->work_type = WORK_TYPE_VOICE;
|
||
}
|
||
scr_data->img_rec_running = false;
|
||
scr_data->img_rec_in_progress = false;
|
||
scr_data->img_rec_triggered = false;
|
||
}
|
||
|
||
static void model_voice_on_image_preview(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
uint16_t width = 0;
|
||
uint16_t height = 0;
|
||
int ret = 0;
|
||
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
if (lisa_ui_nav_scr_get_top_id() != LISA_UI_NAV_SCR_ID_HOME) {
|
||
return;
|
||
}
|
||
|
||
/* 如果识图任务正在进行,忽略按键预览 */
|
||
if (scr_data->img_rec_in_progress) {
|
||
LISA_UI_LOGI("Image recognition in progress, ignore button press");
|
||
return;
|
||
}
|
||
|
||
/* 如果识图正在进行,忽略按键事件 */
|
||
if (scr_data->img_rec_running) {
|
||
LISA_UI_LOGI("Image recognition in progress, ignore button press");
|
||
return;
|
||
}
|
||
|
||
if (!model_voice_cloud_is_connected()) {
|
||
lisa_ui_toast_show(_("service disconnected"));
|
||
return;
|
||
}
|
||
|
||
if (!model_camera_is_inited()) {
|
||
ret = model_camera_init();
|
||
if (ret != 0) {
|
||
LISA_UI_LOGE("Camera init failed: %d", ret);
|
||
lisa_ui_toast_show(_("recognition error"));
|
||
return;
|
||
}
|
||
}
|
||
|
||
ret = model_camera_get_framesize(&width, &height);
|
||
if (ret != 0 || width == 0 || height == 0) {
|
||
LISA_UI_LOGE("Camera unavailable for preview: ret=%d, w=%u, h=%u", ret, width, height);
|
||
lisa_ui_toast_show(_("recognition error"));
|
||
return;
|
||
}
|
||
|
||
lisa_ui_llm_primary_img_hint_hide(scr_data->view);
|
||
|
||
/* 识图过程中不显示本地唤醒提示文案 */
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, "");
|
||
scr_data->work_type = WORK_TYPE_IMG_REC;
|
||
scr_data->img_rec_is_mcp = model_voice_img_rec_is_mcp() ? 1 : 0;
|
||
scr_data->img_rec_is_button = model_voice_img_rec_is_mcp() ? 0 : 1;
|
||
|
||
standby_text_timer_update(scr_data);
|
||
|
||
if (scr_data->camera_capture_timer == NULL) {
|
||
scr_data->camera_capture_timer = lv_timer_create(camera_capture_timer_callback, 60, scr_data);
|
||
} else {
|
||
lv_timer_resume(scr_data->camera_capture_timer);
|
||
}
|
||
|
||
scr_data->img_rec_running = true;
|
||
scr_data->img_rec_in_progress = false;
|
||
scr_data->img_rec_triggered = false;
|
||
LISA_UI_LOGI("Image recognition started, flag set");
|
||
}
|
||
|
||
static void model_voice_on_image_url(void *arg, const char *url)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
if (!scr_data || !scr_data->view || !url || url[0] == '\0') {
|
||
return;
|
||
}
|
||
|
||
if (lisa_ui_nav_scr_get_top_id() != LISA_UI_NAV_SCR_ID_HOME) {
|
||
return;
|
||
}
|
||
|
||
if (scr_data->img_hide_timer) {
|
||
lv_timer_del(scr_data->img_hide_timer);
|
||
scr_data->img_hide_timer = NULL;
|
||
}
|
||
|
||
lv_img_dsc_t *img_dsc = lv_img_net_load(url);
|
||
if (!img_dsc) {
|
||
LISA_UI_LOGE("load net image failed: %s", url);
|
||
return;
|
||
}
|
||
|
||
if (scr_data->net_img) {
|
||
lv_img_net_free(scr_data->net_img);
|
||
scr_data->net_img = NULL;
|
||
}
|
||
|
||
scr_data->net_img = img_dsc;
|
||
lisa_ui_llm_primary_img_show(scr_data->view, scr_data->net_img);
|
||
lisa_ui_llm_primary_img_hint_show(scr_data->view, "图片可在小聆AI小程序中查看");
|
||
scr_data->work_type = WORK_TYPE_IMG_REC;
|
||
scr_data->img_rec_running = false;
|
||
scr_data->img_rec_in_progress = false;
|
||
scr_data->img_rec_triggered = false;
|
||
LISA_UI_LOGI("display net image success");
|
||
}
|
||
|
||
static void model_voice_on_info_show(void *arg)
|
||
{
|
||
if (lisa_ui_nav_scr_get_top_id() != LISA_UI_NAV_SCR_ID_INFO) {
|
||
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_INFO);
|
||
}
|
||
}
|
||
|
||
static void model_voice_on_show_qrcode(void *arg)
|
||
{
|
||
LISA_UI_LOGI("Show qrcode callback, navigating to quota qrcode page");
|
||
if (lisa_ui_nav_scr_get_top_id() != LISA_UI_NAV_SCR_ID_QUOTA_QRCODE) {
|
||
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_QUOTA_QRCODE);
|
||
} else {
|
||
LISA_UI_LOGI("Already on quota qrcode page, skip navigation");
|
||
}
|
||
}
|
||
|
||
#ifdef CONFIG_OTA
|
||
static void model_voice_on_ota_state_change(const ota_state_t *state, void *arg)
|
||
{
|
||
if (!state) {
|
||
return;
|
||
}
|
||
|
||
if (state->state == OTA_STATE_CHECKING) {
|
||
// lisa_ui_toast_show("正在检查更新…");
|
||
} else if (state->state == OTA_STATE_UPDATING) {
|
||
if (lisa_ui_nav_scr_get_top_id() != LISA_UI_NAV_SCR_ID_OTA) {
|
||
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_OTA);
|
||
}
|
||
} else if (state->state == OTA_STATE_UP_TO_DATE) {
|
||
// lisa_ui_toast_show("已是最新版本");
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static void anim_timer_callback(lv_timer_t *timer)
|
||
{
|
||
struct home_nav_scr_data *d = timer->user_data;
|
||
lv_timer_pause(timer);
|
||
|
||
show_emoji_anim(d, EMOJI_NAME_WAKEUP, 0);
|
||
|
||
d->mcp_emoji_running = false;
|
||
}
|
||
|
||
static void wifi_status_timer_callback(lv_timer_t *timer)
|
||
{
|
||
struct home_nav_scr_data *d = timer->user_data;
|
||
|
||
model_wifi_status_t wifi_status = model_wifi_get_status();
|
||
|
||
if (wifi_status == MODEL_WIFI_STATUS_CONNECTED) {
|
||
lisa_ui_llm_primary_set_wifi_img(d->view, &icons_ic_status_wifi_lvl_3_png);
|
||
} else {
|
||
lisa_ui_llm_primary_set_wifi_img(d->view, &icons_ic_status_wifi_no_connect_png);
|
||
if (lisa_ui_nav_scr_get_top_id() == LISA_UI_NAV_SCR_ID_HOME) {
|
||
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_INFO);
|
||
}
|
||
}
|
||
|
||
home_update_alarm_icon(d);
|
||
}
|
||
|
||
static void model_voice_on_image_rec(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
uint16_t width = 0;
|
||
uint16_t height = 0;
|
||
uint16_t image_width = 0;
|
||
uint16_t image_height = 0;
|
||
uint32_t image_size = 0;
|
||
|
||
if (lisa_ui_nav_scr_get_top_id() != LISA_UI_NAV_SCR_ID_HOME) {
|
||
LISA_UI_LOGI("on image recv, skip");
|
||
return;
|
||
}
|
||
|
||
/* 异步识图任务进行中时,忽略后续识图触发 */
|
||
if (scr_data->img_rec_in_progress) {
|
||
LISA_UI_LOGI("on image rec, in progress, ignore duplicate");
|
||
return;
|
||
}
|
||
|
||
/* 双重检查锁定:先检查triggered标志,只有第一个请求能设置它 */
|
||
if (scr_data->img_rec_triggered) {
|
||
LISA_UI_LOGI("on image rec, already triggered, ignore duplicate");
|
||
return;
|
||
}
|
||
scr_data->img_rec_triggered = true;
|
||
|
||
if (scr_data->img_rec_running == false) {
|
||
LISA_UI_LOGI("on image rec, img rec running: %d", scr_data->img_rec_running);
|
||
scr_data->img_rec_triggered = false;
|
||
return;
|
||
}
|
||
|
||
/* 立即暂停定时器并清除运行标志,防止重复触发识别 */
|
||
if (scr_data->camera_capture_timer) {
|
||
lv_timer_pause(scr_data->camera_capture_timer);
|
||
}
|
||
scr_data->img_rec_running = false;
|
||
LISA_UI_LOGI("Image recognition triggered, flag cleared to prevent duplicate");
|
||
|
||
if (scr_data->img_hide_timer) {
|
||
lv_timer_del(scr_data->img_hide_timer);
|
||
scr_data->img_hide_timer = NULL;
|
||
}
|
||
|
||
scr_data->img_rec_is_button = model_voice_img_rec_is_mcp() ? 0 : 1;
|
||
|
||
standby_text_timer_update(scr_data);
|
||
|
||
if (scr_data->camera_capture_timer != NULL) {
|
||
lv_timer_pause(scr_data->camera_capture_timer);
|
||
LISA_UI_LOGI("Camera capture timer paused");
|
||
} else {
|
||
LISA_UI_LOGI("Camera capture timer is NULL");
|
||
}
|
||
|
||
if (scr_data->cap_buf == NULL) {
|
||
LISA_UI_LOGI("scr_data->cap_buf is null");
|
||
scr_data->img_rec_triggered = false;
|
||
return;
|
||
}
|
||
|
||
int frame_ret = model_camera_get_framesize(&width, &height);
|
||
if (frame_ret != 0 || width == 0 || height == 0) {
|
||
LISA_UI_LOGE("Get frame size failed before recognition: ret=%d, w=%u, h=%u", frame_ret, width, height);
|
||
lisa_ui_toast_show(_("recognition error"));
|
||
hide_img_now(scr_data);
|
||
return;
|
||
}
|
||
|
||
image_size = (uint32_t)width * (uint32_t)height * 2U;
|
||
if (image_size == 0 || scr_data->cap_buf_size < image_size) {
|
||
LISA_UI_LOGE("Invalid image buffer size: cap=%u, need=%u", scr_data->cap_buf_size, image_size);
|
||
lisa_ui_toast_show(_("recognition error"));
|
||
hide_img_now(scr_data);
|
||
return;
|
||
}
|
||
|
||
image_width = width;
|
||
image_height = height;
|
||
|
||
int rotate_ret = rotate_rgb565_cw90_inplace(scr_data->cap_buf, width, height);
|
||
if (rotate_ret == 0) {
|
||
image_width = height;
|
||
image_height = width;
|
||
} else {
|
||
LISA_UI_LOGW("Rotate captured image failed: %d", rotate_ret);
|
||
}
|
||
image_size = (uint32_t)image_width * (uint32_t)image_height * 2U;
|
||
|
||
/* Ensure the captured image is displayed before recognition */
|
||
scr_data->img.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||
scr_data->img.header.always_zero = 0;
|
||
scr_data->img.header.reserved = 0;
|
||
scr_data->img.data = scr_data->cap_buf;
|
||
scr_data->img.data_size = image_size;
|
||
scr_data->img.header.w = image_width;
|
||
scr_data->img.header.h = image_height;
|
||
lisa_ui_llm_primary_img_show(scr_data->view, &scr_data->img);
|
||
LISA_UI_LOGI("Image displayed for recognition");
|
||
|
||
if (!model_voice_cloud_is_connected()) {
|
||
lisa_ui_toast_show(_("Please wake me"));
|
||
scr_data->img_rec_triggered = false;
|
||
return;
|
||
} else {
|
||
int r = model_voice_img_recognition(scr_data->cap_buf, image_size, image_width, image_height);
|
||
if (r == 0) {
|
||
scr_data->img_rec_in_progress = true;
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("recognizing"));
|
||
LISA_UI_LOGI("Image recognition started");
|
||
} else {
|
||
lisa_ui_toast_show(_("recognition error"));
|
||
scr_data->img_rec_triggered = false;
|
||
scr_data->img_rec_running = true;
|
||
if (scr_data->camera_capture_timer) {
|
||
lv_timer_resume(scr_data->camera_capture_timer);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void model_voice_on_tts_player_playing(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
scr_data->speaking = 1;
|
||
|
||
if (scr_data->finished) {
|
||
return;
|
||
}
|
||
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("speaking"));
|
||
|
||
standby_text_timer_update(scr_data);
|
||
}
|
||
|
||
static void model_voice_on_tts_player_stoped(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
scr_data->speaking = 0;
|
||
|
||
if (scr_data->img_rec_is_button) {
|
||
if (model_voice_cloud_is_running()) {
|
||
voice_cloud_chat_stop();
|
||
}
|
||
|
||
hide_img_now(scr_data);
|
||
scr_data->img_rec_is_button = 0;
|
||
|
||
if (!scr_data->finished) {
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_NEUTRAL, 0);
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, model_voice_role_propmt_get());
|
||
scr_data->finished = 1;
|
||
}
|
||
|
||
const char *init_status =
|
||
model_voice_cloud_is_connected() ? _("Please wake me") : _("service disconnected");
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, init_status);
|
||
|
||
standby_text_timer_update(scr_data);
|
||
return;
|
||
}
|
||
|
||
if (scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
/* Hide image after TTS playback completes (both single and full duplex mode) */
|
||
hide_img_now(scr_data);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_NEUTRAL, 0);
|
||
|
||
/* In full duplex mode, set status to listening if session is still running */
|
||
if (model_voice_cloud_is_running()) {
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("listening"));
|
||
} else {
|
||
const char *init_status =
|
||
model_voice_cloud_is_connected() ? _("Please wake me") : _("service disconnected");
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, init_status);
|
||
}
|
||
} else {
|
||
if (model_voice_cloud_is_running()) {
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("listening"));
|
||
} else {
|
||
if (!scr_data->finished) {
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_NEUTRAL, 0);
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, model_voice_role_propmt_get());
|
||
scr_data->finished = 1;
|
||
}
|
||
const char *init_status =
|
||
model_voice_cloud_is_connected() ? _("Please wake me") : _("service disconnected");
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, init_status);
|
||
}
|
||
}
|
||
|
||
standby_text_timer_update(scr_data);
|
||
}
|
||
|
||
static void model_voice_on_emoji(void *arg, const char *name)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
if (scr_data->mcp_emoji_running) {
|
||
LISA_UI_LOGI("ignore emoji display, mcp emoji is running");
|
||
return;
|
||
}
|
||
|
||
if (scr_data->finished) {
|
||
return;
|
||
}
|
||
|
||
LISA_UI_LOGI("on emoji, name: %s", name);
|
||
|
||
show_emoji_anim(scr_data, name, 0);
|
||
|
||
lv_timer_reset(scr_data->anim_timer);
|
||
lv_timer_resume(scr_data->anim_timer);
|
||
}
|
||
|
||
static void model_voice_on_mcp_emoji(void *arg, const char *name)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
if (scr_data->finished) {
|
||
return;
|
||
}
|
||
|
||
LISA_UI_LOGI("on emoji, name: %s", name);
|
||
scr_data->mcp_emoji_running = true;
|
||
show_emoji_anim(scr_data, name, 0);
|
||
|
||
lv_timer_reset(scr_data->anim_timer);
|
||
lv_timer_resume(scr_data->anim_timer);
|
||
}
|
||
|
||
static void model_voice_on_mcp_loading(void *arg, bool is_loading, const char *loading_text)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
if (is_loading) {
|
||
scr_data->mcp_loading = 1;
|
||
scr_data->mcp_emoji_running = 1;
|
||
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_WAIT, 1);
|
||
|
||
if (loading_text && loading_text[0] != '\0') {
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, loading_text);
|
||
}
|
||
return;
|
||
}
|
||
|
||
scr_data->mcp_loading = 0;
|
||
scr_data->mcp_emoji_running = 0;
|
||
|
||
|
||
}
|
||
|
||
static void model_voice_on_connected(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (lisa_ui_nav_scr_get_top_id() != LISA_UI_NAV_SCR_ID_HOME) {
|
||
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_HOME);
|
||
}
|
||
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("Please wake me"));
|
||
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, model_voice_role_propmt_get());
|
||
}
|
||
|
||
static void model_voice_on_disconnected(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (scr_data->finish_check_timer) {
|
||
lv_timer_del(scr_data->finish_check_timer);
|
||
scr_data->finish_check_timer = NULL;
|
||
}
|
||
|
||
scr_data->finished = 1;
|
||
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
|
||
show_emoji_anim(scr_data, EMOJI_NAME_NEUTRAL, 0);
|
||
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("service disconnected"));
|
||
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, model_voice_role_propmt_get());
|
||
}
|
||
|
||
static void model_voice_on_start(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (scr_data->finish_check_timer) {
|
||
LISA_UI_LOGI("cancel stale finish timer on session start");
|
||
lv_timer_del(scr_data->finish_check_timer);
|
||
scr_data->finish_check_timer = NULL;
|
||
}
|
||
|
||
lisa_ui_llm_primary_img_hide(scr_data->view);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_WAKEUP, 1);
|
||
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("listening"));
|
||
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, _("Please speak"));
|
||
|
||
scr_data->finished = 0;
|
||
scr_data->work_type = WORK_TYPE_VOICE;
|
||
|
||
standby_text_timer_update(scr_data);
|
||
|
||
}
|
||
|
||
static void enter_standby(struct home_nav_scr_data *scr_data)
|
||
{
|
||
LISA_UI_LOGI("enter_standby");
|
||
|
||
scr_data->finished = 1;
|
||
|
||
/* For image recognition, don't process finish event immediately */
|
||
/* Wait for TTS to complete, then handle in on_tts_player_stoped */
|
||
if (scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
lisa_ui_llm_primary_img_hide(scr_data->view);
|
||
scr_data->work_type = WORK_TYPE_VOICE;
|
||
scr_data->img_rec_running = false;
|
||
scr_data->img_rec_in_progress = false;
|
||
scr_data->img_rec_triggered = false;
|
||
}
|
||
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, model_voice_role_propmt_get());
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_NEUTRAL, 0);
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("Please wake me"));
|
||
standby_text_timer_update(scr_data);
|
||
}
|
||
|
||
static void finished_tiemr_callback(lv_timer_t *timer)
|
||
{
|
||
struct home_nav_scr_data *scr_data = timer->user_data;
|
||
uint8_t running = model_voice_cloud_is_running();
|
||
|
||
if (scr_data->finish_check_timer == timer) {
|
||
scr_data->finish_check_timer = NULL;
|
||
}
|
||
|
||
LISA_UI_LOGI("finished_tiemr_callback, speaking: %d, running: %d",
|
||
scr_data->speaking,
|
||
running);
|
||
|
||
if (!scr_data->speaking && !running) {
|
||
enter_standby(scr_data);
|
||
} else if (running) {
|
||
LISA_UI_LOGI("ignore stale finish timer while session is running");
|
||
}
|
||
|
||
lv_timer_del(timer);
|
||
}
|
||
|
||
static void model_voice_on_finished(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
uint8_t mode = model_voice_wakeup_mode_get();
|
||
|
||
LISA_UI_LOGI("on finished, speaking: %d, wakeup mode: %d", scr_data->speaking, mode);
|
||
|
||
if (mode) {
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_NEUTRAL, 0);
|
||
}
|
||
|
||
if (scr_data->speaking) {
|
||
return;
|
||
} else {
|
||
if (mode) {
|
||
LISA_UI_LOGI("on finished, single mode, check speaking status after 1000ms");
|
||
if (scr_data->finish_check_timer) {
|
||
lv_timer_del(scr_data->finish_check_timer);
|
||
}
|
||
scr_data->finish_check_timer = lv_timer_create(finished_tiemr_callback, 1000, scr_data);
|
||
return;
|
||
}
|
||
}
|
||
|
||
enter_standby(scr_data);
|
||
}
|
||
|
||
static void model_voice_on_tts_text_start(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
}
|
||
|
||
static void model_voice_on_tts_text_end(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
}
|
||
|
||
static void model_voice_on_tts_text_update(const char *text, void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (!scr_data || !scr_data->view || scr_data->finished) {
|
||
return;
|
||
}
|
||
|
||
if (!text || text[0] == '\0') {
|
||
return;
|
||
}
|
||
|
||
if (scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
hide_img_now(scr_data);
|
||
}
|
||
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, text);
|
||
}
|
||
|
||
static void model_voice_on_iat_text_start(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (scr_data->finished) {
|
||
return;
|
||
}
|
||
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("listening"));
|
||
}
|
||
|
||
static void model_voice_on_iat_text_end(void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (scr_data->finished) {
|
||
return;
|
||
}
|
||
|
||
if (!scr_data->speaking) {
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("listening"));
|
||
}
|
||
}
|
||
|
||
static void model_voice_on_iat_text_update(const char *text, void *arg)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (scr_data->finished) {
|
||
return;
|
||
}
|
||
|
||
if (!text || text[0] == '\0') {
|
||
return;
|
||
}
|
||
|
||
if (scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
hide_img_now(scr_data);
|
||
}
|
||
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, text);
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("listening"));
|
||
}
|
||
|
||
static void model_voice_on_standby_text_update(void *arg, const char *text, bool is_cloud_text)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (!scr_data || !scr_data->view || !text) {
|
||
return;
|
||
}
|
||
|
||
if (scr_data->img_rec_running || scr_data->work_type == WORK_TYPE_IMG_REC) {
|
||
return;
|
||
}
|
||
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, text);
|
||
}
|
||
|
||
static void model_voice_on_wakeup_mode_changed(void *arg, model_voice_wakeup_mode_t mode)
|
||
{
|
||
struct home_nav_scr_data *scr_data = arg;
|
||
|
||
if (!scr_data || !scr_data->view) {
|
||
return;
|
||
}
|
||
|
||
bool is_full_duplex = (mode == MODEL_VOICE_WAKEUP_MODE_VOICE_MULTI);
|
||
lisa_ui_llm_primary_set_full_duplex_icon_visible(scr_data->view, is_full_duplex);
|
||
}
|
||
|
||
#ifndef CONFIG_BOARD_ARCS_MINI
|
||
static void settings_btn_event_cb(lv_event_t *e)
|
||
{
|
||
lv_event_code_t code = lv_event_get_code(e);
|
||
|
||
if (code == LV_EVENT_CLICKED) {
|
||
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_SETTING);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static void show_emoji_anim(struct home_nav_scr_data *d, const char *emoji_name, uint8_t imm)
|
||
{
|
||
const lisa_ui_anim_ext_config_t *anim_config = emoji_anim_get_by_name(emoji_name);
|
||
if (anim_config == NULL) {
|
||
return;
|
||
}
|
||
|
||
lv_obj_t *anim = lisa_ui_llm_primary_emoji_anim_get(d->view);
|
||
|
||
if (imm) {
|
||
lisa_ui_anim_ext_next_imm(anim, anim_config);
|
||
} else {
|
||
lisa_ui_anim_ext_next(anim, anim_config);
|
||
}
|
||
}
|
||
|
||
static void home_reset(struct home_nav_scr_data *scr_data)
|
||
{
|
||
const char *iat_text = model_voice_last_iat_text_get();
|
||
|
||
hide_img_now(scr_data);
|
||
if (model_voice_cloud_is_running()) {
|
||
if (iat_text && iat_text[0] != '\0') {
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, iat_text);
|
||
} else {
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, _("Please speak"));
|
||
}
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, _("listening"));
|
||
} else {
|
||
lisa_ui_llm_primary_set_content_text(scr_data->view, model_voice_role_propmt_get());
|
||
const char *init_status = model_voice_cloud_is_connected() ? _("Please wake me") : _("service disconnected");
|
||
lisa_ui_llm_primary_set_status_text(scr_data->view, init_status);
|
||
}
|
||
home_update_full_duplex_icon(scr_data);
|
||
home_update_alarm_icon(scr_data);
|
||
home_update_battery_icon(scr_data);
|
||
show_emoji_anim(scr_data, EMOJI_NAME_NEUTRAL, true);
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
|
||
standby_text_timer_update(scr_data);
|
||
}
|
||
|
||
static int home_nav_scr_open(const struct lisa_ui_nav_scr *scr, void **data)
|
||
{
|
||
LISA_UI_LOGD("nav scr open, id: %d", scr->unique_id);
|
||
|
||
model_voice_init();
|
||
#if !CONFIG_4G_MODULE
|
||
int cam_init_ret = model_camera_init();
|
||
if (cam_init_ret != 0) {
|
||
LISA_UI_LOGW("Camera init failed on home open: %d", cam_init_ret);
|
||
}
|
||
#endif
|
||
model_wifi_init();
|
||
|
||
struct home_nav_scr_data *scr_data = lisa_ui_malloc(sizeof(struct home_nav_scr_data));
|
||
if (!scr_data) {
|
||
LISA_UI_LOGE("failed to allocate memory for home_nav_scr_data");
|
||
return -1;
|
||
}
|
||
memset(scr_data, 0, sizeof(struct home_nav_scr_data));
|
||
|
||
scr_data->view = lisa_ui_llm_primary_create(lv_scr_act());
|
||
#ifndef CONFIG_BOARD_ARCS_MINI
|
||
lisa_ui_llm_primary_set_settings_icon_event_cb(scr_data->view, settings_btn_event_cb, NULL);
|
||
#endif
|
||
|
||
scr_data->anim_timer = lv_timer_create(anim_timer_callback, 5000, scr_data);
|
||
lv_timer_pause(scr_data->anim_timer);
|
||
lisa_ui_llm_primary_set_wifi_img(scr_data->view, &icons_ic_status_wifi_no_connect_png);
|
||
scr_data->wifi_status_timer = lv_timer_create(wifi_status_timer_callback, 200, scr_data);
|
||
scr_data->standby_text_timer = NULL;
|
||
|
||
model_battery_cb_register(model_battery_on_battery_status_update, scr_data);
|
||
home_update_battery_icon(scr_data);
|
||
|
||
*data = scr_data;
|
||
|
||
model_voice_cb_register(&model_voice_cbs, scr_data);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int home_nav_scr_show(const struct lisa_ui_nav_scr *scr, void *data)
|
||
{
|
||
LISA_UI_LOGD("Home nav scr show, id: %d", scr->unique_id);
|
||
|
||
struct home_nav_scr_data *scr_data = (struct home_nav_scr_data *)data;
|
||
if (!scr_data || !scr_data->view) {
|
||
LISA_UI_LOGE("Invalid home nav scr data");
|
||
return -1;
|
||
}
|
||
|
||
lv_obj_clear_flag(scr_data->view, LV_OBJ_FLAG_HIDDEN);
|
||
|
||
model_voice_on();
|
||
home_reset(scr_data);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int home_nav_scr_pause(const struct lisa_ui_nav_scr *scr, void *data)
|
||
{
|
||
LISA_UI_LOGD("nav scr pause, id: %d", scr->unique_id);
|
||
|
||
struct home_nav_scr_data *scr_data = (struct home_nav_scr_data *)data;
|
||
if (scr_data && scr_data->view) {
|
||
lv_obj_add_flag(scr_data->view, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
|
||
if (scr_data && scr_data->wifi_status_timer) {
|
||
lv_timer_pause(scr_data->wifi_status_timer);
|
||
}
|
||
|
||
if (scr_data && scr_data->camera_capture_timer) {
|
||
lv_timer_pause(scr_data->camera_capture_timer);
|
||
}
|
||
|
||
/* 页面切换时重置识图标志,确保下次返回可以继续识图 */
|
||
if (scr_data) {
|
||
scr_data->img_rec_running = false;
|
||
scr_data->img_rec_in_progress = false;
|
||
scr_data->img_rec_triggered = false;
|
||
LISA_UI_LOGI("Page paused, img_rec_running flag reset");
|
||
}
|
||
|
||
|
||
if (!model_voice_cloud_is_running()) {
|
||
model_voice_off();
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int home_nav_scr_resume(const struct lisa_ui_nav_scr *scr, void *data)
|
||
{
|
||
LISA_UI_LOGD("nav scr resume, id: %d", scr->unique_id);
|
||
|
||
struct home_nav_scr_data *scr_data = (struct home_nav_scr_data *)data;
|
||
if (scr_data && scr_data->view) {
|
||
lv_obj_clear_flag(scr_data->view, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
|
||
if (scr_data && scr_data->wifi_status_timer) {
|
||
lv_timer_resume(scr_data->wifi_status_timer);
|
||
}
|
||
|
||
model_voice_on();
|
||
home_reset(scr_data);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int home_nav_scr_close(const struct lisa_ui_nav_scr *scr, void *data)
|
||
{
|
||
LISA_UI_LOGD("nav scr close, id: %d", scr->unique_id);
|
||
|
||
model_voice_cb_unregister(&model_voice_cbs);
|
||
model_battery_cb_unregister(model_battery_on_battery_status_update);
|
||
|
||
struct home_nav_scr_data *scr_data = (struct home_nav_scr_data *)data;
|
||
if (scr_data) {
|
||
if (scr_data->anim_timer != NULL) {
|
||
lv_timer_del(scr_data->anim_timer);
|
||
scr_data->anim_timer = NULL;
|
||
}
|
||
if (scr_data->wifi_status_timer != NULL) {
|
||
lv_timer_del(scr_data->wifi_status_timer);
|
||
scr_data->wifi_status_timer = NULL;
|
||
}
|
||
if (scr_data->camera_capture_timer != NULL) {
|
||
lv_timer_del(scr_data->camera_capture_timer);
|
||
scr_data->camera_capture_timer = NULL;
|
||
}
|
||
if (scr_data->standby_text_timer != NULL) {
|
||
lv_timer_del(scr_data->standby_text_timer);
|
||
scr_data->standby_text_timer = NULL;
|
||
}
|
||
if (scr_data->cap_buf != NULL) {
|
||
lisa_ui_free(scr_data->cap_buf);
|
||
scr_data->cap_buf = NULL;
|
||
scr_data->cap_buf_size = 0;
|
||
}
|
||
if (scr_data->net_img != NULL) {
|
||
// lv_img_net_free(scr_data->net_img);
|
||
scr_data->net_img = NULL;
|
||
}
|
||
if (scr_data->view) {
|
||
lv_obj_del(scr_data->view);
|
||
}
|
||
lisa_ui_free(scr_data);
|
||
}
|
||
|
||
model_voice_off();
|
||
|
||
return 0;
|
||
}
|
||
|
||
const struct lisa_ui_nav_scr home_nav_scr = {
|
||
.unique_id = LISA_UI_NAV_SCR_ID_HOME,
|
||
.open = home_nav_scr_open,
|
||
.show = home_nav_scr_show,
|
||
.pause = home_nav_scr_pause,
|
||
.resume = home_nav_scr_resume,
|
||
.close = home_nav_scr_close,
|
||
};
|