chore: migrate project into clean repository

This commit is contained in:
yuuux
2026-08-13 16:50:52 +08:00
commit d1d25a09e7
27405 changed files with 9422808 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
target_sources(
${LISA_UI_LIB_NAME}
PRIVATE
model_voice.c
model_common.c
model_qrcode.c
model_wifi.c
model_battery.c
model_camera.c
model_alarm.c
)
if(CONFIG_OTA)
target_sources(${LISA_UI_LIB_NAME} PRIVATE model_ota.c)
endif()
target_include_directories(
${LISA_UI_LIB_NAME}
PRIVATE
./
)

View File

@@ -0,0 +1,332 @@
/**
* @file model_alarm.c
* @brief Alarm data model implementation
*/
#include "model_alarm.h"
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <sys/time.h>
#include "lisa_ui.h"
#include "lisa_ui_invoke.h"
#include "lisa_ui_nav_scr_ids.h"
#ifdef LISA_UI_PLATFORM_ARCS
#include "voice_msg.h"
#include "service_alarm.h"
#include "app_datas.h"
#define ALARM_TRIGGER_NAV_DELAY_MS 200
#endif
#define TAG "model_alarm"
/**
* @brief Alarm model context
*/
typedef struct {
alarm_data_t data;
char event_text[128];
bool initialized;
bool is_ringing; /* 闹钟是否正在响铃 */
uint32_t alarm_count;
} alarm_model_ctx_t;
static alarm_model_ctx_t g_alarm_ctx = {0};
#ifdef LISA_UI_PLATFORM_ARCS
static void alarm_trigger_nav_ui_worker(void *arg, uint32_t len)
{
if (!arg || len < sizeof(struct service_alarm)) {
LISA_UI_LOGE("Model: invalid alarm trigger payload");
return;
}
const struct service_alarm *alarm = (const struct service_alarm *)arg;
LISA_UI_LOGI("Model: Alarm triggered - timestamp: %llu", alarm->timestamp);
g_alarm_ctx.data.timestamp = alarm->timestamp;
g_alarm_ctx.event_text[0] = '\0';
g_alarm_ctx.initialized = true;
g_alarm_ctx.is_ringing = true;
int top_id = lisa_ui_nav_scr_get_top_id();
if (top_id == LISA_UI_NAV_SCR_ID_ALARM_RING) {
LISA_UI_LOGI("Already on alarm ring page, skipping navigation");
return;
}
if (top_id != LISA_UI_NAV_SCR_ID_HOME) {
LISA_UI_LOGI("Model: Navigating to home before alarm ring, top_id=%d", top_id);
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_HOME);
}
LISA_UI_LOGI("Model: Navigating to alarm ring page");
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_ALARM_RING);
}
static void on_alarm_create_event(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
(void)unused;
(void)msg_id;
(void)user_data;
struct service_alarm *alarm = (struct service_alarm *)data;
LISA_UI_LOGI("Model: Alarm created - timestamp: %llu", alarm->timestamp);
g_alarm_ctx.alarm_count++;
LISA_UI_INVOKE_UI_ARG_PTR(alarm, sizeof(struct service_alarm), {
g_alarm_ctx.data.timestamp = _invoke_alarm->timestamp;
g_alarm_ctx.event_text[0] = '\0';
g_alarm_ctx.initialized = true;
if (lisa_ui_nav_scr_get_top_id() == LISA_UI_NAV_SCR_ID_ALARM_SUCCESS) {
LISA_UI_LOGI("Already on alarm success page, skipping navigation");
return;
}
lisa_ui_nav_scr_nav_to(LISA_UI_NAV_SCR_ID_ALARM_SUCCESS);
});
}
static void on_alarm_delete_event(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
(void)unused;
(void)msg_id;
(void)data;
(void)len;
(void)user_data;
if (g_alarm_ctx.alarm_count > 0) {
g_alarm_ctx.alarm_count--;
}
}
static void on_alarm_query_event(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
(void)unused;
(void)msg_id;
(void)user_data;
if (!data || len < sizeof(uint32_t)) {
return;
}
g_alarm_ctx.alarm_count = *(uint32_t *)data;
}
static void on_alarm_trigger_event(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
(void)unused;
(void)msg_id;
(void)user_data;
struct service_alarm *alarm = (struct service_alarm *)data;
if (!alarm) {
LISA_UI_LOGE("Model: alarm trigger data is NULL");
return;
}
#ifdef LISA_UI_PLATFORM_ARCS
struct app_datas *app_datas = get_app_datas();
if (app_datas) {
LISA_UI_LOGI("Model: Before setting - can_wakeup=%d, voice_work_mode=%d",
app_datas->can_wakeup, app_datas->voice_work_mode);
app_datas->can_wakeup = true;
app_datas->voice_work_mode |= VOICE_WORK_MODE_VOICE_WAKEUP;
LISA_UI_LOGI("Model: After setting - can_wakeup=%d, voice_work_mode=%d",
app_datas->can_wakeup, app_datas->voice_work_mode);
} else {
LISA_UI_LOGE("Model: Failed to get app_datas!");
}
/* Alarm page switch can race with MCP session UI transition; stop MCP first. */
voice_msg_pub(VOICE_MSG_CLOUD_MCP_CHAT_EXIT, NULL, 0);
if (lisa_ui_invoke_ui_delayed(alarm_trigger_nav_ui_worker, alarm, sizeof(struct service_alarm),
ALARM_TRIGGER_NAV_DELAY_MS) != 0) {
LISA_UI_LOGE("Model: failed to schedule alarm ring navigation");
}
#endif
}
#endif
int model_alarm_init(void)
{
if (g_alarm_ctx.initialized) {
return 0;
}
LISA_UI_LOGI("Initializing alarm model");
#ifdef LISA_UI_PLATFORM_ARCS
voice_msg_sub(VOICE_MSG_ALARM_CREATE, on_alarm_create_event, NULL);
voice_msg_sub(VOICE_MSG_ALARM_DELETE, on_alarm_delete_event, NULL);
voice_msg_sub(VOICE_MSG_ALARM_QUERY, on_alarm_query_event, NULL);
voice_msg_sub(VOICE_MSG_ALARM_TRIGGER, on_alarm_trigger_event, NULL);
uint32_t cnt = 0;
struct service_alarm *service_alarms = service_alarm_get_all(&cnt);
if (service_alarms) {
lisa_mem_free(service_alarms);
}
g_alarm_ctx.alarm_count = cnt;
#endif
g_alarm_ctx.initialized = true;
LISA_UI_LOGI("Alarm model initialized");
return 0;
}
void model_alarm_set_data(const alarm_data_t *data)
{
if (!data) {
LISA_UI_LOGE("Invalid alarm data");
return;
}
g_alarm_ctx.data = *data;
LISA_UI_LOGI("Alarm data set - timestamp: %llu", (unsigned long long)data->timestamp);
}
int model_alarm_get_data(alarm_data_t *data)
{
if (!data) {
LISA_UI_LOGE("Invalid output parameter");
return -1;
}
if (!g_alarm_ctx.initialized) {
LISA_UI_LOGW("Alarm model not initialized");
return -1;
}
*data = g_alarm_ctx.data;
return 0;
}
const char *model_alarm_get_event_text(void)
{
return g_alarm_ctx.event_text;
}
int model_alarm_get_time_info(uint64_t timestamp, alarm_time_info_t *info)
{
if (!info) {
LISA_UI_LOGE("Invalid parameter");
return -1;
}
struct timeval tv;
time_t current_time = time(NULL);
if (gettimeofday(&tv, NULL) == 0) {
current_time = tv.tv_sec;
}
time_t alarm_time = (time_t)timestamp;
struct tm alarm_tm_struct;
struct tm *alarm_tm = localtime_r(&alarm_time, &alarm_tm_struct);
if (!alarm_tm) {
LISA_UI_LOGE("Failed to convert timestamp");
return -1;
}
info->year = alarm_tm->tm_year + 1970;
info->month = alarm_tm->tm_mon + 1;
info->day = alarm_tm->tm_mday;
info->hour = alarm_tm->tm_hour;
info->minute = alarm_tm->tm_min;
int64_t local_ts = (int64_t)timestamp + (int64_t)8 * 3600;
int64_t days_since_epoch = local_ts / 86400;
info->weekday = (int)((4 + (days_since_epoch % 7) + 7) % 7);
int64_t alarm_local_days = ((int64_t)timestamp + (int64_t)8 * 3600) / 86400;
int64_t current_local_days = ((int64_t)current_time + (int64_t)8 * 3600) / 86400;
int64_t day_diff = alarm_local_days - current_local_days;
info->is_today = (day_diff == 0);
info->is_tomorrow = (day_diff == 1);
return 0;
}
int model_alarm_delete_by_timestamp(uint64_t timestamp)
{
#ifdef LISA_UI_PLATFORM_ARCS
int r = ls_alarm_delete_by_timestamp(timestamp);
return r;
#endif
return 0;
}
void model_alarm_free(void *alarms)
{
if (alarms) {
lisa_ui_free(alarms);
}
}
uint32_t model_alarm_count_get(void)
{
return g_alarm_ctx.alarm_count;
}
alarm_data_t *model_alarm_get_all(uint32_t *cnt)
{
if (!cnt) {
LISA_UI_LOGE("Invalid parameter: cnt is NULL");
return NULL;
}
#ifdef LISA_UI_PLATFORM_ARCS
if (!service_alarm_init_done()) {
*cnt = 0;
return NULL;
}
uint32_t alarm_count = 0;
struct service_alarm *service_alarms = service_alarm_get_all(&alarm_count);
*cnt = alarm_count;
if (alarm_count == 0 || service_alarms == NULL) {
return NULL;
}
// Allocate memory for alarm_data_t array
alarm_data_t *alarms = lisa_ui_malloc(sizeof(alarm_data_t) * alarm_count);
if (alarms == NULL) {
LISA_UI_LOGE("Failed to allocate memory for alarms");
lisa_mem_free(service_alarms);
*cnt = 0;
return NULL;
}
// Convert service_alarm to alarm_data_t
for (uint32_t i = 0; i < alarm_count; i++) {
alarms[i].timestamp = service_alarms[i].timestamp;
LISA_UI_LOGI("alarm %d, timestamp:%llu", i, alarms[i].timestamp);
}
// Free the service_alarm array
lisa_mem_free(service_alarms);
LISA_UI_LOGI("Got %u alarms", alarm_count);
return alarms;
#else
*cnt = 0;
return NULL;
#endif
}

View File

@@ -0,0 +1,46 @@
/**
* @file model_alarm.h
* @brief Alarm data model header
*/
#ifndef __MODEL_ALARM_H__
#define __MODEL_ALARM_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
typedef struct {
uint64_t timestamp;
} alarm_data_t;
typedef struct {
int year;
int month;
int day;
int hour;
int minute;
int weekday;
bool is_today;
bool is_tomorrow;
} alarm_time_info_t;
void model_alarm_set_data(const alarm_data_t *data);
int model_alarm_get_data(alarm_data_t *data);
int model_alarm_get_time_info(uint64_t timestamp, alarm_time_info_t *info);
const char *model_alarm_get_event_text(void);
int model_alarm_init(void);
int model_alarm_delete_by_timestamp(uint64_t timestamp);
alarm_data_t *model_alarm_get_all(uint32_t *cnt);
void model_alarm_free(void *alarms);
uint32_t model_alarm_count_get(void);
#ifdef __cplusplus
}
#endif
#endif /* __MODEL_ALARM_H__ */

View File

@@ -0,0 +1,132 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#define TAG "model_battery"
#include "lisa_ui_invoke.h"
#include "lisa_ui_log.h"
#include "model_battery.h"
#ifdef LISA_UI_PLATFORM_ARCS
#include "voice_msg.h"
#endif
struct model_battery_context {
uint32_t inited: 1;
model_battery_info_t info;
model_battery_update_cb_t on_battery_status_update;
void *arg;
};
static struct model_battery_context model_battery_ctx = {
.inited = 0,
.info = {
.level = 0,
.status = MODEL_BATTERY_STATUS_UNKNOWN,
},
.on_battery_status_update = NULL,
.arg = NULL,
};
#ifdef LISA_UI_PLATFORM_ARCS
static model_battery_status_t convert_battery_status(voice_msg_battery_status_t status)
{
switch (status) {
case VOICE_MSG_BATTERY_STATUS_NO_BATTERY:
return MODEL_BATTERY_STATUS_NO_BATTERY;
case VOICE_MSG_BATTERY_STATUS_NOT_CONNECT:
return MODEL_BATTERY_STATUS_NOT_CONNECT;
case VOICE_MSG_BATTERY_STATUS_CHARGING:
return MODEL_BATTERY_STATUS_CHARGING;
case VOICE_MSG_BATTERY_STATUS_CHARGE_DONE:
return MODEL_BATTERY_STATUS_CHARGE_DONE;
case VOICE_MSG_BATTERY_STATUS_UNKNOWN:
default:
return MODEL_BATTERY_STATUS_UNKNOWN;
}
}
static void battery_update_msg_handle(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
(void)unused;
(void)msg_id;
(void)user_data;
if (data == NULL || len < sizeof(voice_msg_battery_info_t)) {
return;
}
voice_msg_battery_info_t msg = *(voice_msg_battery_info_t *)data;
LISA_UI_INVOKE_UI_ARG_BASE(msg, {
model_battery_ctx.info.level = _invoke_msg.level;
model_battery_ctx.info.status = convert_battery_status((voice_msg_battery_status_t)_invoke_msg.status);
if (model_battery_ctx.on_battery_status_update) {
model_battery_ctx.on_battery_status_update(&model_battery_ctx.info, model_battery_ctx.arg);
}
});
}
#endif /* LISA_UI_PLATFORM_ARCS */
int model_battery_init(void)
{
if (model_battery_ctx.inited) {
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
voice_msg_sub(VOICE_MSG_POWER_BATTERY_UPDATE, battery_update_msg_handle, NULL);
#endif
model_battery_ctx.inited = 1;
return 0;
}
int model_battery_deinit(void)
{
if (!model_battery_ctx.inited) {
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
voice_msg_unsub(VOICE_MSG_POWER_BATTERY_UPDATE, battery_update_msg_handle);
#endif
model_battery_ctx.inited = 0;
model_battery_ctx.on_battery_status_update = NULL;
model_battery_ctx.arg = NULL;
return 0;
}
int model_battery_get_info(model_battery_info_t *info)
{
if (info == NULL) {
return -1;
}
*info = model_battery_ctx.info;
return 0;
}
int model_battery_cb_register(model_battery_update_cb_t cb, void *arg)
{
model_battery_ctx.on_battery_status_update = cb;
model_battery_ctx.arg = arg;
return 0;
}
int model_battery_cb_unregister(model_battery_update_cb_t cb)
{
(void)cb;
model_battery_ctx.on_battery_status_update = NULL;
model_battery_ctx.arg = NULL;
return 0;
}

View File

@@ -0,0 +1,41 @@
/**
* @file model_battery.h
* @brief Battery data model interface
*/
#ifndef __MODEL_BATTERY_H__
#define __MODEL_BATTERY_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
typedef enum {
MODEL_BATTERY_STATUS_NO_BATTERY = 0,
MODEL_BATTERY_STATUS_NOT_CONNECT,
MODEL_BATTERY_STATUS_CHARGING,
MODEL_BATTERY_STATUS_CHARGE_DONE,
MODEL_BATTERY_STATUS_UNKNOWN,
} model_battery_status_t;
typedef struct {
uint8_t level; /* 0-100 */
model_battery_status_t status;
} model_battery_info_t;
typedef void (*model_battery_update_cb_t)(const model_battery_info_t *info, void *arg);
int model_battery_init(void);
int model_battery_deinit(void);
int model_battery_get_info(model_battery_info_t *info);
int model_battery_cb_register(model_battery_update_cb_t cb, void *arg);
int model_battery_cb_unregister(model_battery_update_cb_t cb);
#ifdef __cplusplus
}
#endif
#endif /* __MODEL_BATTERY_H__ */

View File

@@ -0,0 +1,163 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#define TAG "model_camera"
#include "lisa_ui_invoke.h"
#include "lisa_ui_log.h"
#ifdef LISA_UI_PLATFORM_ARCS
#include "service_camera.h"
#include "voice_msg.h"
#endif
struct model_camera_context {
uint32_t inited: 1;
uint32_t running: 1;
};
static struct model_camera_context model_camera_ctx = {
.inited = 0,
.running = 0,
};
#ifdef LISA_UI_PLATFORM_ARCS
static void bn_camera_hw_init(void *data, uint32_t len, struct voice_invoke_rsp *rsp)
{
int r = service_camera_init();
rsp->err = r;
}
#endif
int model_camera_init(void)
{
if (model_camera_ctx.inited) {
LISA_UI_LOGW("Camera model already initialized");
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
struct voice_invoke_rsp rsp = {
.err = -1,
};
int r = voice_invoke_sync(bn_camera_hw_init, NULL, 0, &rsp, 1000);
if (r) {
LISA_UI_LOGE("bn_camera_hw_init sync invoke failed, r:%d", r);
return r;
} else {
if (rsp.err) {
LISA_UI_LOGE("bn_camera_hw_init sync invoke resp err, r:%d", rsp.err);
return rsp.err;
}
}
#else
LISA_UI_LOGW("Camera not supported on this platform");
return -5;
#endif
model_camera_ctx.inited = 1;
LISA_UI_LOGD("Camera model initialized");
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
struct service_camera_rsp {
struct voice_invoke_rsp base;
uint8_t *cap_buf;
uint32_t buf_len;
};
static void bn_camera_capture(void *data, uint32_t data_len, struct voice_invoke_rsp *rsp)
{
struct service_camera_rsp *camera_rsp = (struct service_camera_rsp *)rsp;
if (camera_rsp->cap_buf == NULL) {
LISA_UI_LOGE("cap buf is null");
camera_rsp->base.err = -1;
return;
}
int ret = service_camera_capture(camera_rsp->cap_buf, camera_rsp->buf_len);
camera_rsp->base.err = ret;
}
#endif
int model_camera_capture(uint8_t *in, uint32_t len)
{
#ifdef LISA_UI_PLATFORM_ARCS
if (!model_camera_ctx.inited) {
LISA_UI_LOGE("Camera model not initialized");
return -1;
}
if (in == NULL || len == 0) {
LISA_UI_LOGE("Invalid capture buffer");
return -2;
}
struct service_camera_rsp rsp = {
.base = {
.err = -1,
},
.cap_buf = in,
.buf_len = len,
};
int r = voice_invoke_sync(bn_camera_capture, NULL, 0, (struct voice_invoke_rsp *)&rsp, 1000);
if (r) {
LISA_UI_LOGE("camera cap invoke sync failed, r: %d", r);
return r;
} else {
if (rsp.base.err) {
LISA_UI_LOGE("camera cap invoke sync rsp err, r: %d", rsp.base.err);
return rsp.base.err;
}
}
#else
LISA_UI_LOGW("Camera not supported on this platform");
return -5;
#endif
return 0;
}
int model_camera_get_framesize(uint16_t *width, uint16_t *height)
{
if (!width || !height) {
LISA_UI_LOGE("Invalid framesize output parameters");
return -2;
}
*width = 0;
*height = 0;
#ifdef LISA_UI_PLATFORM_ARCS
if (!model_camera_ctx.inited) {
LISA_UI_LOGE("Camera model not initialized");
return -1;
}
int r = service_camera_get_framesize(width, height);
if (r != 0) {
*width = 0;
*height = 0;
}
return r;
#else
return 0;
#endif
}
bool model_camera_is_running(void)
{
return model_camera_ctx.running;
}
bool model_camera_is_inited(void)
{
return model_camera_ctx.inited;
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2025, LISTENAI
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __MODEL_CAMERA_H__
#define __MODEL_CAMERA_H__
#include <stdint.h>
#include <stdbool.h>
int model_camera_init(void);
int model_camera_capture(uint8_t *in, uint32_t len);
int model_camera_get_framesize(uint16_t *width, uint16_t *height);
bool model_camera_is_running(void);
bool model_camera_is_inited(void);
#endif // __MODEL_CAMERA_H__

View File

@@ -0,0 +1,109 @@
#include "model_common.h"
#include "lisa_ui.h"
#ifdef LISA_UI_PLATFORM_ARCS
#include "lisa_ui_invoke.h"
#include "service_brightness.h"
#include "service_volume.h"
#endif
#define TAG "model_common"
struct model_common_context {
uint8_t volume;
uint8_t brightness;
uint8_t inited;
};
static struct model_common_context model_common_ctx = {
.volume = 70,
.brightness = 70,
.inited = 0,
};
#ifdef LISA_UI_PLATFORM_ARCS
static void model_volume_set_invoke_bn(void *arg, uint32_t arg_len)
{
uint8_t volume = *(uint8_t *)arg;
service_volume_set(volume);
}
static void model_brightness_set_invoke_bn(void *arg, uint32_t arg_len)
{
uint8_t brightness = *(uint8_t *)arg;
service_brightness_set(brightness);
}
#endif
int model_common_init(void)
{
if (model_common_ctx.inited) {
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
model_common_ctx.brightness = service_brightness_get();
model_common_ctx.volume = service_volume_get();
model_common_ctx.inited = 1;
#endif
LISA_UI_LOGD("Common model initialized");
return 0;
}
int model_common_sync_from_system(void)
{
#ifdef LISA_UI_PLATFORM_ARCS
model_common_ctx.volume = service_volume_get();
model_common_ctx.brightness = service_brightness_get();
return 0;
#else
return -1;
#endif
}
uint8_t model_common_volume_get(void)
{
return model_common_ctx.volume;
}
int model_common_volume_set(uint8_t volume)
{
if (volume > 100) {
volume = 100;
}
model_common_ctx.volume = volume;
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_INVOKE_BN(model_volume_set_invoke_bn, &volume, sizeof(volume));
#endif
LISA_UI_LOGD("Volume set to: %d", volume);
return 0;
}
uint8_t model_common_brightness_get(void)
{
return model_common_ctx.brightness;
}
int model_common_brightness_set(uint8_t brightness)
{
if (brightness > 100) {
LISA_UI_LOGE("Invalid brightness: %d", brightness);
return -1;
}
model_common_ctx.brightness = brightness;
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_INVOKE_BN(model_brightness_set_invoke_bn, &brightness, sizeof(brightness));
#endif
LISA_UI_LOGD("Brightness set to: %d", brightness);
return 0;
}

View File

@@ -0,0 +1,44 @@
#ifndef __MODEL_COMMON_H__
#define __MODEL_COMMON_H__
#include <stdint.h>
/**
* @brief Get current volume level
* @return Volume level (0-100)
*/
uint8_t model_common_volume_get(void);
/**
* @brief Set volume level
* @param volume Volume level (0-100)
* @return 0 on success, -1 on error
*/
int model_common_volume_set(uint8_t volume);
/**
* @brief Get current brightness level
* @return Brightness level (0-100)
*/
uint8_t model_common_brightness_get(void);
/**
* @brief Set brightness level
* @param brightness Brightness level (0-100)
* @return 0 on success, -1 on error
*/
int model_common_brightness_set(uint8_t brightness);
/**
* @brief Initialize common model
* @return 0 on success, -1 on error
*/
int model_common_init(void);
/**
* @brief Synchronize current state from system services
* @return 0 on success, -1 if unavailable
*/
int model_common_sync_from_system(void);
#endif

View File

@@ -0,0 +1,47 @@
#include "model_ota.h"
#include "lisa_ui.h"
#include "lisa_ui_invoke.h"
#include "lisa_ui_nav_scr_ids.h"
#include "voice_msg.h"
#include "ota_manager.h"
struct model_ota_context {
const struct model_ota_cb *cbs;
void *arg;
};
static struct model_ota_context model_ota_ctx;
static void handle_ota_state_change(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
ota_state_t *state = (ota_state_t *)data;
LISA_UI_INVOKE_UI_ARG_PTR(state, sizeof(ota_state_t), {
if (model_ota_ctx.cbs && model_ota_ctx.cbs->on_ota_state_change) {
model_ota_ctx.cbs->on_ota_state_change(_invoke_state, model_ota_ctx.arg);
}
});
}
int model_ota_init(void)
{
voice_msg_sub(VOICE_MSG_OTA_UPDATING, handle_ota_state_change, NULL);
voice_msg_sub(VOICE_MSG_OTA_SUCCESSED, handle_ota_state_change, NULL);
voice_msg_sub(VOICE_MSG_OTA_FAILED, handle_ota_state_change, NULL);
return 0;
}
int model_ota_cb_register(const struct model_ota_cb *cb, void *arg)
{
model_ota_ctx.cbs = cb;
model_ota_ctx.arg = arg;
return 0;
}
int model_ota_cb_unregister(const struct model_ota_cb *cb)
{
model_ota_ctx.cbs = NULL;
model_ota_ctx.arg = NULL;
return 0;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "ota_manager.h"
struct model_ota_cb {
void (*on_ota_state_change)(const ota_state_t *state, void *arg);
};
int model_ota_init(void);
int model_ota_cb_register(const struct model_ota_cb *cb, void *arg);
int model_ota_cb_unregister(const struct model_ota_cb *cb);

View File

@@ -0,0 +1,624 @@
/**
* @file model_qrcode.c
* @brief QR code data model implementation
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define TAG "model_qrcode"
#include "lisa_ui.h"
#include "lisa_ui_invoke.h"
#include "model_wifi.h"
#include "model_qrcode.h"
#ifdef LISA_UI_PLATFORM_ARCS
#include "bt_app_if.h"
#include "lisa_thread.h"
#include "HTTPCUsr_api.h"
#include "voice_msg.h"
#include "lvgl.h"
#include "cJSON.h"
#include "app_datas.h"
#include "lisa_ui_invoke.h"
#include "app_net_cfg.h"
#endif
LV_IMG_DECLARE(ble_qr);
/**
* @brief QR code model context
*/
typedef struct {
bool initialized;
qrcode_status_t status;
const char *top_text;
const char *bottom_text;
const void *qr_image;
lv_img_dsc_t dynamic_qrcode_img;
char role_setting_qrcode_url[512];
uint8_t *role_setting_qrcode_img_raw;
uint32_t role_setting_qrcode_img_raw_size;
char device_id[64];
char device_id_text[72];
char quota_qrcode_url[512];
uint8_t *quota_qrcode_img_raw;
uint32_t quota_qrcode_img_raw_size;
char quota_message[256];
char quota_err_code[64];
lv_img_dsc_t quota_qrcode_img;
} qrcode_model_ctx_t;
static qrcode_model_ctx_t g_qrcode_ctx = {0};
// Default text strings
static const char *DEFAULT_NOT_CONNECTED_TEXT = "当前网络未连接\n请先完成配网";
static const char *DEFAULT_CONNECTED_TEXT = "请使用微信扫码修改配置";
static const char *DEFAULT_OUT_OF_LIMIT_TEXT = "今日交互额度已用完";
static const char *DEFAULT_OUT_OF_LIMIT_BOTTOM_TEXT = "微信扫码开通会员\n获取更多交互额度";
#ifdef LISA_UI_PLATFORM_ARCS
static void qrcode_download_thread(void *arg)
{
char *url = (char *)arg;
int ret = 0;
uint8_t *download_buffer = NULL;
uint32_t buffer_size = 0;
unsigned int received = 0;
unsigned int total_received = 0;
HTTPParameters *http_param = (HTTPParameters *)lisa_ui_malloc(sizeof(HTTPParameters));
if (!http_param) {
LISA_UI_LOGE("Failed to allocate HTTP parameters");
return;
}
memset(http_param, 0, sizeof(HTTPParameters));
if (strncmp(url, "https", 5) == 0) {
strcpy(http_param->Uri, "http");
strcat(http_param->Uri, url + 5);
LISA_UI_LOGI("Convert HTTPS to HTTP: %s -> %s", url, http_param->Uri);
} else {
strcpy(http_param->Uri, url);
}
http_param->HttpVerb = VerbGet;
http_param->nTimeout = 30;
http_param->pData = NULL;
http_param->pLength = 0;
if ((ret = HTTPC_open(http_param)) != 0) {
LISA_UI_LOGE("Failed to open HTTP connection: %d", ret);
goto cleanup;
}
if ((ret = HTTPC_request(http_param, NULL)) != 0) {
LISA_UI_LOGE("Failed to send HTTP request: %d", ret);
goto cleanup;
}
HTTP_CLIENT http_client = {0};
if ((ret = HTTPC_get_request_info(http_param, &http_client)) != 0) {
LISA_UI_LOGE("Failed to get HTTP response info: %d", ret);
goto cleanup;
}
if (http_client.TotalResponseBodyLength == 0) {
LISA_UI_LOGE("Empty response body");
goto cleanup;
}
buffer_size = http_client.TotalResponseBodyLength;
download_buffer = (uint8_t *)lisa_ui_malloc(buffer_size);
if (!download_buffer) {
LISA_UI_LOGE("Failed to allocate download buffer: %u bytes", buffer_size);
goto cleanup;
}
do {
unsigned int remaining = buffer_size - total_received;
unsigned int to_read = remaining;
UINT32 received = 0;
ret = HTTPC_read(http_param, download_buffer + total_received, to_read, &received);
if (received > 0) {
total_received += received;
}
if (ret != 0) {
break;
}
vTaskDelay(2);
} while (total_received < buffer_size);
if (total_received == buffer_size) {
LISA_UI_LOGI("QR code download completed successfully: %u bytes, %p", total_received, download_buffer);
struct invoke_payload {
uint8_t *img;
uint32_t len;
};
struct invoke_payload payload = {
.img = download_buffer,
.len = total_received,
};
struct invoke_payload *p = &payload;
uint32_t p_len = sizeof(struct invoke_payload);
LISA_UI_INVOKE_UI_ARG_PTR(p, p_len, {
if (g_qrcode_ctx.role_setting_qrcode_img_raw) {
lisa_ui_free(g_qrcode_ctx.role_setting_qrcode_img_raw);
}
LISA_UI_LOGI("set role setting qrcode, img:%p, size: %d", _invoke_p->img, _invoke_p->len);
g_qrcode_ctx.role_setting_qrcode_img_raw = _invoke_p->img;
g_qrcode_ctx.role_setting_qrcode_img_raw_size = _invoke_p->len;
});
} else {
LISA_UI_LOGE("Download incomplete: %u/%u bytes", total_received, buffer_size);
}
cleanup:
HTTPC_close(http_param);
if (http_param) {
lisa_ui_free(http_param);
}
if (download_buffer) {
/* 不需要释放download_buffer */
}
LISA_UI_LOGD("QR code download thread exiting");
}
static int modify_qrcode_url(const char *input_url, char *output_url, size_t output_size)
{
if (!input_url || !output_url || output_size == 0) {
LISA_UI_LOGE("Invalid parameters");
return -1;
}
const char *question_mark = strchr(input_url, '?');
if (question_mark) {
size_t base_url_len = question_mark - input_url + 1;
const char *new_params = "x-oss-process=image/resize,m_pad,w_148,limit_0,color_000000/quality,q_100/format,jpg";
size_t new_params_len = strlen(new_params);
if (base_url_len + new_params_len + 1 > output_size) {
LISA_UI_LOGE("Output buffer too small");
return -1;
}
memcpy(output_url, input_url, base_url_len);
strcpy(output_url + base_url_len, new_params);
LISA_UI_LOGI("Modified URL: %s -> %s", input_url, output_url);
return 0;
} else {
const char *new_params = "x-oss-process=image/resize,m_pad,w_148,limit_0,color_000000/quality,q_100/format,jpg";
size_t input_len = strlen(input_url);
size_t new_params_len = strlen(new_params);
size_t total_len = input_len + 1 + new_params_len + 1;
if (total_len > output_size) {
LISA_UI_LOGE("Output buffer too small for modified URL");
return -1;
}
strcpy(output_url, input_url);
output_url[input_len] = '?';
strcpy(output_url + input_len + 1, new_params);
LISA_UI_LOGI("Modified URL (add params): %s -> %s", input_url, output_url);
return 0;
}
}
static void download_qrcode_start(void)
{
static uint8_t qr_download_name[] = "qr_download";
lisa_thread_attr_t thread_attr = {
.name = qr_download_name,
.stack_size = 1024 * 16,
.priority = 5,
};
lisa_thread_t *download_thread =
lisa_thread_create(&thread_attr, qrcode_download_thread, (void *)g_qrcode_ctx.role_setting_qrcode_url);
if (!download_thread) {
LISA_UI_LOGE("Failed to create QR code download thread");
} else {
LISA_UI_LOGD("QR code download thread created successfully");
}
}
static void voice_cloud_role_setting_qrcode_handle(void *unused, uint32_t msg_id, void *data, uint32_t len,
void *user_data)
{
if (!data || len == 0 || len >= sizeof(g_qrcode_ctx.role_setting_qrcode_url)) {
LISA_UI_LOGE("Invalid QR code URL data");
return;
}
if (g_qrcode_ctx.status == QR_STATUS_OUT_OF_LIMIT) {
LISA_UI_LOGI("Ignore role setting qrcode due to out-of-limit status");
return;
}
char *url = (char *)data;
LISA_UI_INVOKE_UI_ARG_PTR(url, len, {
int ret = modify_qrcode_url(_invoke_url, g_qrcode_ctx.role_setting_qrcode_url,
sizeof(g_qrcode_ctx.role_setting_qrcode_url));
if (ret == 0) {
download_qrcode_start();
} else {
LISA_UI_LOGE("Failed to modify QR code URL");
}
});
}
static void quota_qrcode_download_thread(void *arg)
{
char *url = (char *)arg;
int ret = 0;
uint8_t *download_buffer = NULL;
uint32_t buffer_size = 0;
unsigned int total_received = 0;
HTTPParameters *http_param = (HTTPParameters *)lisa_ui_malloc(sizeof(HTTPParameters));
if (!http_param) {
LISA_UI_LOGE("Failed to allocate HTTP parameters");
return;
}
memset(http_param, 0, sizeof(HTTPParameters));
if (strncmp(url, "https", 5) == 0) {
strcpy(http_param->Uri, "http");
strcat(http_param->Uri, url + 5);
LISA_UI_LOGI("Convert HTTPS to HTTP: %s -> %s", url, http_param->Uri);
} else {
strcpy(http_param->Uri, url);
}
http_param->HttpVerb = VerbGet;
http_param->nTimeout = 30;
http_param->pData = NULL;
http_param->pLength = 0;
if ((ret = HTTPC_open(http_param)) != 0) {
LISA_UI_LOGE("Failed to open HTTP connection: %d", ret);
goto cleanup;
}
if ((ret = HTTPC_request(http_param, NULL)) != 0) {
LISA_UI_LOGE("Failed to send HTTP request: %d", ret);
goto cleanup;
}
HTTP_CLIENT http_client = {0};
if ((ret = HTTPC_get_request_info(http_param, &http_client)) != 0) {
LISA_UI_LOGE("Failed to get HTTP response info: %d", ret);
goto cleanup;
}
if (http_client.TotalResponseBodyLength == 0) {
LISA_UI_LOGE("Empty response body");
goto cleanup;
}
buffer_size = http_client.TotalResponseBodyLength;
download_buffer = (uint8_t *)lisa_ui_malloc(buffer_size);
if (!download_buffer) {
LISA_UI_LOGE("Failed to allocate download buffer: %u bytes", buffer_size);
goto cleanup;
}
do {
unsigned int remaining = buffer_size - total_received;
unsigned int to_read = remaining;
UINT32 received = 0;
ret = HTTPC_read(http_param, download_buffer + total_received, to_read, &received);
if (received > 0) {
total_received += received;
}
if (ret != 0) {
break;
}
vTaskDelay(2);
} while (total_received < buffer_size);
if (total_received == buffer_size) {
LISA_UI_LOGI("Quota QR code download completed: %u bytes, %p", total_received, download_buffer);
struct invoke_payload {
uint8_t *img;
uint32_t len;
};
struct invoke_payload payload = {
.img = download_buffer,
.len = total_received,
};
struct invoke_payload *p = &payload;
uint32_t p_len = sizeof(struct invoke_payload);
LISA_UI_INVOKE_UI_ARG_PTR(p, p_len, {
uint8_t *old_img = g_qrcode_ctx.quota_qrcode_img_raw;
LISA_UI_LOGI("Set quota qrcode, img:0x%p, size: %u", _invoke_p->img, _invoke_p->len);
g_qrcode_ctx.quota_qrcode_img_raw = _invoke_p->img;
g_qrcode_ctx.quota_qrcode_img_raw_size = _invoke_p->len;
memset(&g_qrcode_ctx.quota_qrcode_img, 0, sizeof(lv_img_dsc_t));
g_qrcode_ctx.quota_qrcode_img.header.cf = LV_IMG_CF_RAW;
g_qrcode_ctx.quota_qrcode_img.header.always_zero = 0;
g_qrcode_ctx.quota_qrcode_img.header.reserved = 0;
g_qrcode_ctx.quota_qrcode_img.header.w = 0;
g_qrcode_ctx.quota_qrcode_img.header.h = 0;
g_qrcode_ctx.quota_qrcode_img.data_size = g_qrcode_ctx.quota_qrcode_img_raw_size;
g_qrcode_ctx.quota_qrcode_img.data = g_qrcode_ctx.quota_qrcode_img_raw;
if (old_img) {
LISA_UI_LOGI("Freeing old quota qrcode image: 0x%p", old_img);
lisa_ui_free(old_img);
}
});
download_buffer = NULL;
} else {
LISA_UI_LOGE("Quota QR code download incomplete: %u/%u bytes", total_received, buffer_size);
}
cleanup:
if (download_buffer) {
lisa_ui_free(download_buffer);
}
if (http_param) {
HTTPC_close(http_param);
lisa_ui_free(http_param);
}
}
static void download_quota_qrcode_start(void)
{
static uint8_t quota_qr_dl_name[] = "quota_qr_dl";
lisa_thread_attr_t attr = {
.name = quota_qr_dl_name,
.priority = 8,
.stack_size = 8192,
};
lisa_thread_create(&attr, quota_qrcode_download_thread, g_qrcode_ctx.quota_qrcode_url);
}
struct auth_failed_rsp {
int err;
uint8_t auth_failed;
};
static void get_auth_failed_worker(void *data, uint32_t len, struct voice_invoke_rsp *rsp)
{
struct auth_failed_rsp *auth_rsp = (struct auth_failed_rsp *)rsp;
struct app_datas *app_datas = get_app_datas();
if (app_datas) {
auth_rsp->err = 0;
auth_rsp->auth_failed = app_datas->auth_failed;
} else {
auth_rsp->err = -1;
auth_rsp->auth_failed = 0;
}
}
static void voice_cloud_show_qrcode_handle(void *unused, uint32_t msg_id, void *data, uint32_t len,
void *user_data)
{
if (!data || len == 0) {
LISA_UI_LOGE("Invalid show qrcode data");
return;
}
char *json_str = (char *)data;
LISA_UI_LOGI("Show qrcode received: %s", json_str);
LISA_UI_INVOKE_UI_ARG_PTR(json_str, len, {
cJSON *json = cJSON_Parse(_invoke_json_str);
if (!json) {
LISA_UI_LOGE("Failed to parse show qrcode JSON");
return;
}
cJSON *url_json = cJSON_GetObjectItem(json, "url");
cJSON *message_json = cJSON_GetObjectItem(json, "message");
cJSON *err_code_json = cJSON_GetObjectItem(json, "err_code");
char url_buf[512] = {0};
char message_buf[256] = {0};
char err_code_buf[64] = {0};
if (url_json && url_json->valuestring) {
strncpy(url_buf, url_json->valuestring, sizeof(url_buf) - 1);
}
if (message_json && message_json->valuestring) {
strncpy(message_buf, message_json->valuestring, sizeof(message_buf) - 1);
} else {
strncpy(message_buf, DEFAULT_OUT_OF_LIMIT_TEXT, sizeof(message_buf) - 1);
}
if (err_code_json && err_code_json->valuestring) {
strncpy(err_code_buf, err_code_json->valuestring, sizeof(err_code_buf) - 1);
}
cJSON_Delete(json);
if (url_buf[0] == '\0') {
LISA_UI_LOGE("No URL in show qrcode JSON");
return;
}
LISA_UI_LOGI("Parsed - url: %s, message: %s, err_code: %s", url_buf, message_buf, err_code_buf);
int ret = modify_qrcode_url(url_buf, g_qrcode_ctx.quota_qrcode_url,
sizeof(g_qrcode_ctx.quota_qrcode_url));
if (ret == 0) {
strncpy(g_qrcode_ctx.quota_message, message_buf,
sizeof(g_qrcode_ctx.quota_message) - 1);
g_qrcode_ctx.quota_message[sizeof(g_qrcode_ctx.quota_message) - 1] = '\0';
strncpy(g_qrcode_ctx.quota_err_code, err_code_buf,
sizeof(g_qrcode_ctx.quota_err_code) - 1);
g_qrcode_ctx.quota_err_code[sizeof(g_qrcode_ctx.quota_err_code) - 1] = '\0';
g_qrcode_ctx.status = QR_STATUS_OUT_OF_LIMIT;
download_quota_qrcode_start();
LISA_UI_LOGI("Quota qrcode download started");
} else {
LISA_UI_LOGE("Failed to modify quota QR code URL");
}
});
}
#endif
int model_qrcode_init(void)
{
if (g_qrcode_ctx.initialized) {
return 0;
}
g_qrcode_ctx.initialized = true;
#ifdef LISA_UI_PLATFORM_ARCS
voice_msg_sub(VOICE_MSG_CLOUD_ROLE_SETTING_QRCODE, voice_cloud_role_setting_qrcode_handle, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_SHOW_QRCODE, voice_cloud_show_qrcode_handle, NULL);
LISA_UI_LOGI("Subscribed to VOICE_MSG_CLOUD_SHOW_QRCODE");
#endif
return 0;
}
qrcode_status_t model_qrcode_get_status(void)
{
return g_qrcode_ctx.status;
}
int model_qrcode_get_quota_data(qrcode_data_t *data)
{
if (!data) {
LISA_UI_LOGE("Invalid parameter");
return -1;
}
if (!g_qrcode_ctx.initialized) {
LISA_UI_LOGW("QR code model not initialized, initializing now");
model_qrcode_init();
}
data->status = QR_STATUS_OUT_OF_LIMIT;
data->top_text = g_qrcode_ctx.quota_message[0] ? g_qrcode_ctx.quota_message : DEFAULT_OUT_OF_LIMIT_TEXT;
data->bottom_text = DEFAULT_OUT_OF_LIMIT_BOTTOM_TEXT;
data->qr_image = g_qrcode_ctx.quota_qrcode_img_raw ? &g_qrcode_ctx.quota_qrcode_img : NULL;
LISA_UI_LOGI("Returning quota qrcode data, top_text: %s, bottom_text: %s, img: %p",
data->top_text, data->bottom_text, data->qr_image);
return 0;
}
int model_qrcode_get_config_data(qrcode_data_t *data)
{
if (!data) {
LISA_UI_LOGE("Invalid parameter");
return -1;
}
if (!g_qrcode_ctx.initialized) {
LISA_UI_LOGW("QR code model not initialized, initializing now");
model_qrcode_init();
}
model_wifi_status_t wifi_sta = model_wifi_get_status();
if (wifi_sta == MODEL_WIFI_STATUS_DISCONNECTED || wifi_sta == MODEL_WIFI_STATUS_UNKNOWN) {
data->status = QR_STATUS_NOT_CONNECTED;
data->top_text = DEFAULT_NOT_CONNECTED_TEXT;
if (get_current_device_id(g_qrcode_ctx.device_id, sizeof(g_qrcode_ctx.device_id)) == 0) {
snprintf(g_qrcode_ctx.device_id_text, sizeof(g_qrcode_ctx.device_id_text),
"ID: %s", g_qrcode_ctx.device_id);
data->bottom_text = g_qrcode_ctx.device_id_text;
} else {
data->bottom_text = "";
}
data->qr_image = &ble_qr;
#ifdef LISA_UI_PLATFORM_ARCS
app_ble_adv_start(0, BLE_ADV_GEN);
#endif
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
struct auth_failed_rsp auth_rsp = {
.err = -1,
.auth_failed = 0,
};
int r = voice_invoke_sync(get_auth_failed_worker, NULL, 0,
(struct voice_invoke_rsp *)&auth_rsp, 1000);
if (r == 0 && auth_rsp.err == 0 && auth_rsp.auth_failed) {
data->top_text = "云端鉴权失败";
data->bottom_text = "请联系技术对接人添加云端授权";
data->qr_image = NULL;
return 0;
}
#endif
data->status = QR_STATUS_CONNECTED;
if (g_qrcode_ctx.role_setting_qrcode_img_raw == NULL) {
data->top_text = "二维码加载中, 请稍后";
data->bottom_text = "";
data->qr_image = NULL;
return 0;
}
g_qrcode_ctx.dynamic_qrcode_img.header.cf = LV_IMG_CF_RAW;
g_qrcode_ctx.dynamic_qrcode_img.header.always_zero = 0;
g_qrcode_ctx.dynamic_qrcode_img.header.reserved = 0;
g_qrcode_ctx.dynamic_qrcode_img.header.w = 0;
g_qrcode_ctx.dynamic_qrcode_img.header.h = 0;
g_qrcode_ctx.dynamic_qrcode_img.data_size = g_qrcode_ctx.role_setting_qrcode_img_raw_size;
g_qrcode_ctx.dynamic_qrcode_img.data = g_qrcode_ctx.role_setting_qrcode_img_raw;
data->top_text = DEFAULT_CONNECTED_TEXT;
data->bottom_text = "";
data->qr_image = &g_qrcode_ctx.dynamic_qrcode_img;
return 0;
}
int model_qrcode_get_data(qrcode_data_t *data)
{
if (!data) {
LISA_UI_LOGE("Invalid parameter");
return -1;
}
if (!g_qrcode_ctx.initialized) {
LISA_UI_LOGW("QR code model not initialized, initializing now");
model_qrcode_init();
}
if (g_qrcode_ctx.status == QR_STATUS_OUT_OF_LIMIT) {
return model_qrcode_get_quota_data(data);
}
return model_qrcode_get_config_data(data);
}

View File

@@ -0,0 +1,95 @@
/**
* @file model_qrcode.h
* @brief QR code data model interface
*
* Provides data access for QR code display and management.
*/
#ifndef __MODEL_QRCODE_H__
#define __MODEL_QRCODE_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
/**
* @brief QR code status enumeration
*/
typedef enum {
QR_STATUS_NOT_CONNECTED = 0, /**< Network not connected, show BLE QR */
QR_STATUS_CONNECTED, /**< Network connected, show cloud QR */
QR_STATUS_OUT_OF_LIMIT, /**< Usage limit exceeded */
} qrcode_status_t;
/**
* @brief QR code data structure
*/
typedef struct {
qrcode_status_t status; /**< Current QR code status */
const char *top_text; /**< Top instruction text */
const char *bottom_text; /**< Bottom information text */
const void *qr_image; /**< QR code image source (C array) */
} qrcode_data_t;
/**
* @brief Initialize QR code model
*
* @return 0 on success, negative on error
*/
int model_qrcode_init(void);
/**
* @brief Get current QR code status
*
* @return Current QR code status
*/
qrcode_status_t model_qrcode_get_status(void);
/**
* @brief Get QR code data for display
*
* @param data Pointer to receive QR code data
* @return 0 on success, negative on error
*/
int model_qrcode_get_data(qrcode_data_t *data);
int model_qrcode_get_config_data(qrcode_data_t *data);
int model_qrcode_get_quota_data(qrcode_data_t *data);
/**
* @brief Update QR code from cloud (RGB565 format)
*
* @param rgb565_data RGB565 image data
* @param width Image width
* @param height Image height
* @param data_size Data size in bytes
* @return 0 on success, negative on error
*/
int model_qrcode_update_cloud_qr(const uint16_t *rgb565_data, uint16_t width, uint16_t height, uint32_t data_size);
/**
* @brief Set QR code status
*
* @param status New status
* @return 0 on success, negative on error
*/
int model_qrcode_set_status(qrcode_status_t status);
/**
* @brief Set custom text for QR code page
*
* @param top_text Top text (NULL to use default)
* @param bottom_text Bottom text (NULL to use default)
* @return 0 on success, negative on error
*/
int model_qrcode_set_text(const char *top_text, const char *bottom_text);
#ifdef __cplusplus
}
#endif
#endif /* __MODEL_QRCODE_H__ */

View File

@@ -0,0 +1,999 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include "lisa_ui_invoke.h"
#include "lisa_ui_log.h"
#include "model_voice.h"
#ifdef LISA_UI_PLATFORM_ARCS
#include "app_datas.h"
#include "voice_msg.h"
#include "voice_cloud.h"
#include "lisa_kv.h"
#include "kv.h"
#if CONFIG_OTA
#include "kv_sys.h"
#endif
#include "img_helper.h"
#include "async_task.h"
#include "cJSON.h"
#include "lisa_ui_nav_scr_ids.h"
#include "model_alarm.h"
#endif
#define IMG_REC_MODE_LSCHAT 0
#define IMG_REC_MODE_MCP 1
#define MAX_STANDBY_TEXTS 10
#define MAX_TEXT_LENGTH 128
#define MAX_LOADING_TEXT_LENGTH 128
struct model_voice_context {
uint32_t cloud_connected: 1;
uint32_t inited: 1;
uint32_t voice_en: 1;
uint32_t running: 1;
uint32_t tts_playing: 1;
uint32_t img_rec_in_progress: 1;
const struct model_voice_cb *cbs;
model_voice_wakeup_mode_t wakeup_mode;
void *arg;
uint8_t img_rec_mode;
char mcp_id[64];
char prompt[64];
uint32_t img_rec_task_id;
// Standby text rotation
char standby_texts[MAX_STANDBY_TEXTS][MAX_TEXT_LENGTH];
uint32_t standby_text_count;
uint32_t standby_interval_ms;
bool standby_enabled;
#if CONFIG_OTA
char wake_word[20];
#endif
};
static struct model_voice_context model_voice_ctx = {
.voice_en = true,
.inited = 0,
.cloud_connected = 0,
.wakeup_mode = MODEL_VOICE_WAKEUP_MODE_VOICE_SINGLE,
};
static char s_last_iat_text[128] = {0};
static void notify_standby_texts_changed(void);
#ifdef LISA_UI_PLATFORM_ARCS
static void voice_cloud_show_qrcode_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data);
static void voice_cloud_show_qrcode_ui_worker(void *arg, uint32_t arg_len);
#define SHOW_QRCODE_NAV_DELAY_MS 50
#endif
#if defined(CONFIG_OTA) && defined(LISA_UI_PLATFORM_ARCS)
static void model_voice_load_wake_word_from_kv(void)
{
char *wake_word = NULL;
lisa_kv_get_string(KV_KEY_SYS_WAKEWORD, &wake_word);
if (wake_word != NULL && wake_word[0] != '\0') {
strncpy(model_voice_ctx.wake_word, wake_word, sizeof(model_voice_ctx.wake_word) - 1);
model_voice_ctx.wake_word[sizeof(model_voice_ctx.wake_word) - 1] = '\0';
}
lisa_kv_free(wake_word);
}
#endif
const char *model_voice_role_name_get(void)
{
#ifdef CONFIG_OTA
if (strnlen(model_voice_ctx.wake_word, sizeof(model_voice_ctx.wake_word)) > 0) {
return model_voice_ctx.wake_word;
} else {
return "小马采宝";
}
#else
return "小马采宝";
#endif
}
#ifdef CONFIG_OTA
static const char *make_wake_hint_text(const char *base_text)
{
static char wake_hint[MAX_TEXT_LENGTH];
const char *wake_word = model_voice_role_name_get();
if (!base_text) {
return "";
}
const char *placeholder = "#唤醒词#";
char *pos = strstr(base_text, placeholder);
if (pos) {
size_t prefix_len = pos - base_text;
size_t wake_word_len = strlen(wake_word);
size_t suffix_len = strlen(pos + strlen(placeholder));
if (prefix_len + wake_word_len + suffix_len > MAX_TEXT_LENGTH - 1) {
return base_text; // 超出长度限制,返回原文本
}
char *p = wake_hint;
strncpy(p, base_text, prefix_len);
p += prefix_len;
strncpy(p, wake_word, wake_word_len);
p += wake_word_len;
strncpy(p, pos + strlen(placeholder), suffix_len);
p += suffix_len;
*p = '\0';
} else {
strncpy(wake_hint, base_text, MAX_TEXT_LENGTH - 1);
wake_hint[MAX_TEXT_LENGTH - 1] = '\0';
}
return wake_hint;
}
#endif
const char *model_voice_role_propmt_get(void)
{
#ifdef CONFIG_OTA
return make_wake_hint_text(model_voice_ctx.prompt);
#else
return model_voice_ctx.prompt;
#endif
}
model_voice_wakeup_mode_t model_voice_wakeup_mode_get(void)
{
return model_voice_ctx.wakeup_mode;
}
int model_voice_wakeup_mode_set(model_voice_wakeup_mode_t mode)
{
if (mode >= MODEL_VOICE_WAKEUP_MODE_MAX) {
return -1;
}
LISA_UI_LOGI("voice wakeup mode set, mode: %d", mode);
model_voice_ctx.wakeup_mode = mode;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_wakeup_mode_changed) {
model_voice_ctx.cbs->on_wakeup_mode_changed(model_voice_ctx.arg, mode);
}
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_INVOKE_BN_ARG_BASE(mode, {
struct app_datas *app_datas = get_app_datas();
if (app_datas == NULL) {
return;
}
app_datas->voice_work_mode = 0;
if (_invoke_mode == MODEL_VOICE_WAKEUP_MODE_BUTTON) {
app_datas->full_duplex = false;
app_datas->voice_work_mode &= ~VOICE_WORK_MODE_VOICE_WAKEUP;
app_datas->voice_work_mode |= VOICE_WORK_MODE_BUTTON_WAKEUP;
} else if (_invoke_mode == MODEL_VOICE_WAKEUP_MODE_VOICE_MULTI) {
app_datas->full_duplex = true;
app_datas->voice_work_mode &= ~VOICE_WORK_MODE_BUTTON_WAKEUP;
app_datas->voice_work_mode |= VOICE_WORK_MODE_VOICE_WAKEUP;
} else if (_invoke_mode == MODEL_VOICE_WAKEUP_MODE_VOICE_SINGLE) {
app_datas->full_duplex = false;
app_datas->voice_work_mode &= ~VOICE_WORK_MODE_BUTTON_WAKEUP;
app_datas->voice_work_mode |= VOICE_WORK_MODE_VOICE_WAKEUP;
} else {
LISA_UI_LOGW("invalid mode to save, mode: %d", _invoke_mode);
}
voice_cloud_chat_stop();
lisa_kv_set_bool(KV_KEY_FULL_DUPLEX, app_datas->full_duplex);
LISA_UI_LOGI("save voice work mode: %d", (int)app_datas->voice_work_mode);
lisa_kv_set_int(KV_KEY_WAKEUP_MODE, (int)app_datas->voice_work_mode);
});
#endif
return 0;
}
const char *model_voice_wakeup_mode_name_get(model_voice_wakeup_mode_t mode)
{
switch (mode) {
case MODEL_VOICE_WAKEUP_MODE_BUTTON:
return "Button Wakeup";
case MODEL_VOICE_WAKEUP_MODE_VOICE_SINGLE:
return "Voice Wakeup (Single)";
case MODEL_VOICE_WAKEUP_MODE_VOICE_MULTI:
return "Voice Wakeup (Multi)";
default:
return "Unknown";
}
}
#ifdef LISA_UI_PLATFORM_ARCS
static void voice_cloud_tts_txt(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
if (msg_id == VOICE_MSG_CLOUD_TTS_TEXT_START) {
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_tts_text_start) {
model_voice_ctx.cbs->on_tts_text_start(model_voice_ctx.arg);
}
});
} else if (msg_id == VOICE_MSG_CLOUD_TTS_TEXT_UPDATE) {
char *text = (char *)data;
LISA_UI_INVOKE_UI_ARG_PTR(text, len, {
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_tts_text_update) {
model_voice_ctx.cbs->on_tts_text_update(_invoke_text, model_voice_ctx.arg);
}
});
} else if (msg_id == VOICE_MSG_CLOUD_TTS_TEXT_END) {
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_tts_text_end) {
model_voice_ctx.cbs->on_tts_text_end(model_voice_ctx.arg);
}
});
}
}
static void voice_cloud_iat_txt(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
if (msg_id == VOICE_MSG_CLOUD_IAT_START) {
s_last_iat_text[0] = '\0';
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_iat_text_start) {
model_voice_ctx.cbs->on_iat_text_start(model_voice_ctx.arg);
}
});
} else if (msg_id == VOICE_MSG_CLOUD_IAT_UPDATE) {
char *p = (char *)data;
if (p && len > 0 && p[0] != '\0') {
uint32_t cpy_len = len > (sizeof(s_last_iat_text) - 1) ? (sizeof(s_last_iat_text) - 1) : len;
memcpy(s_last_iat_text, p, cpy_len);
s_last_iat_text[cpy_len] = '\0';
}
LISA_UI_INVOKE_UI_ARG_PTR(p, len, {
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_iat_text_update) {
model_voice_ctx.cbs->on_iat_text_update(_invoke_p, model_voice_ctx.arg);
}
});
} else if (msg_id == VOICE_MSG_CLOUD_IAT_END) {
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_iat_text_end) {
model_voice_ctx.cbs->on_iat_text_end(model_voice_ctx.arg);
}
});
}
}
static void voice_cloud_session_starting(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
service_image_waiting_cancel();
s_last_iat_text[0] = '\0';
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.running = 1;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_start) {
model_voice_ctx.cbs->on_start(model_voice_ctx.arg);
}
});
}
static void voice_cloud_session_finished(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.running = 0;
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_LOGI("Audio recognition stopped on session finished");
#endif
LISA_UI_LOGI("Session finished, tts_playing=%d", model_voice_ctx.tts_playing);
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_finished) {
model_voice_ctx.cbs->on_finished(model_voice_ctx.arg);
}
});
}
static void voice_cloud_tts_url_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_LOGI("Audio recognition stopped on TTS URL received");
#endif
}
static void voice_cloud_connected(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.cloud_connected = 1;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_connected) {
model_voice_ctx.cbs->on_connected(model_voice_ctx.arg);
}
});
}
static void voice_cloud_disconnected(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.cloud_connected = 0;
model_voice_ctx.running = 0;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_disconnected) {
model_voice_ctx.cbs->on_disconnected(model_voice_ctx.arg);
}
});
}
static void voice_cloud_emoji_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
char *emoji = (char *)data;
LISA_UI_INVOKE_UI_ARG_PTR(emoji, len, {
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_emoji) {
model_voice_ctx.cbs->on_emoji(model_voice_ctx.arg, _invoke_emoji);
}
});
}
static void voice_cloud_mcp_emoji_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
char *emoji = (char *)data;
LISA_UI_INVOKE_UI_ARG_PTR(emoji, len, {
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_mcp_emoji) {
model_voice_ctx.cbs->on_mcp_emoji(model_voice_ctx.arg, _invoke_emoji);
}
});
}
static void voice_cloud_mcp_loading_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
if (!data || len == 0) {
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_mcp_loading) {
model_voice_ctx.cbs->on_mcp_loading(model_voice_ctx.arg, false, NULL);
}
});
return;
}
char loading_text[MAX_LOADING_TEXT_LENGTH] = {0};
uint32_t copy_len = len > (sizeof(loading_text) - 1) ? (sizeof(loading_text) - 1) : len;
memcpy(loading_text, data, copy_len);
char *text_ptr = loading_text;
LISA_UI_INVOKE_UI_ARG_PTR(text_ptr, strlen(text_ptr) + 1, {
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_mcp_loading) {
model_voice_ctx.cbs->on_mcp_loading(model_voice_ctx.arg, true, _invoke_text_ptr);
}
});
}
static void voice_cloud_tts_player_playing(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.tts_playing = 1;
LISA_UI_LOGI("TTS playing started, tts_playing=1");
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_tts_playing) {
model_voice_ctx.cbs->on_tts_playing(model_voice_ctx.arg);
}
});
}
static void voice_cloud_tts_player_stoped(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.tts_playing = 0;
LISA_UI_LOGI("TTS playing stopped, tts_playing=0, running=%d", model_voice_ctx.running);
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_tts_stoped) {
model_voice_ctx.cbs->on_tts_stoped(model_voice_ctx.arg);
}
});
}
static void voice_button_changed(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
voice_msg_button_evt_t *evt = (voice_msg_button_evt_t *)data;
if (evt->button_id == 1) {
if (evt->action == VOICE_MSG_BUTTON_ACTION_PRESS_DOWN) {
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.img_rec_mode = IMG_REC_MODE_LSCHAT;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_image_preview) {
model_voice_ctx.cbs->on_image_preview(model_voice_ctx.arg);
}
});
} else if (evt->action == VOICE_MSG_BUTTON_ACTION_SHORT_UP
|| evt->action == VOICE_MSG_BUTTON_ACTION_LONG_UP
|| evt->action == VOICE_MSG_BUTTON_ACTION_LONG_HOLD_UP) {
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.img_rec_mode = IMG_REC_MODE_LSCHAT;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_image_rec) {
model_voice_ctx.cbs->on_image_rec(model_voice_ctx.arg);
}
});
}
} else if (evt->button_id == 2 && evt->action == VOICE_MSG_BUTTON_ACTION_CLICK) {
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_info_show) {
model_voice_ctx.cbs->on_info_show(model_voice_ctx.arg);
}
});
}
}
static void voice_mcp_image_recognition_stop_worker(void *arg, uint32_t len)
{
uint32_t task_id = 0;
/* 检查任务ID是否匹配如果不匹配说明已被新任务打断 */
if (arg && len >= sizeof(uint32_t)) {
task_id = *(uint32_t *)arg;
if (task_id != model_voice_ctx.img_rec_task_id) {
LISA_UI_LOGW("Image recognition task %u canceled by newer task %u", task_id, model_voice_ctx.img_rec_task_id);
return;
}
}
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_image_rec) {
model_voice_ctx.cbs->on_image_rec(model_voice_ctx.arg);
}
/* 清除识别进行中标志 */
model_voice_ctx.img_rec_in_progress = 0;
LISA_UI_LOGI("Image recognition task %u completed, flag cleared", model_voice_ctx.img_rec_task_id);
}
static void voice_mcp_image_recognition(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
char *mcp_id = data;
LISA_UI_INVOKE_UI_ARG_PTR(mcp_id, len, {
/* 如果已有识别任务在进行递增任务ID打断上一次 */
if (model_voice_ctx.img_rec_in_progress) {
model_voice_ctx.img_rec_task_id++;
LISA_UI_LOGW("Image recognition already in progress, interrupting with new task %u", model_voice_ctx.img_rec_task_id);
} else {
/* 设置识别进行中标志 */
model_voice_ctx.img_rec_in_progress = 1;
model_voice_ctx.img_rec_task_id++;
LISA_UI_LOGI("Starting image recognition task %u", model_voice_ctx.img_rec_task_id);
}
memcpy(model_voice_ctx.mcp_id, _invoke_mcp_id,
_invoke_len > sizeof(model_voice_ctx.mcp_id) ? sizeof(model_voice_ctx.mcp_id) : _invoke_len);
model_voice_ctx.img_rec_mode = IMG_REC_MODE_MCP;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_image_preview) {
model_voice_ctx.cbs->on_image_preview(model_voice_ctx.arg);
}
/* 启动预览后200ms停止预览并进行图像识别传递当前任务ID */
static uint32_t current_task_id;
current_task_id = model_voice_ctx.img_rec_task_id;
lisa_ui_invoke_ui_delayed(voice_mcp_image_recognition_stop_worker, &current_task_id, sizeof(current_task_id), 200);
});
}
static void voice_button_image_recognition(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_voice_ctx.img_rec_mode = IMG_REC_MODE_LSCHAT;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_image_preview) {
model_voice_ctx.cbs->on_image_preview(model_voice_ctx.arg);
}
/* 延迟触发识别,给预览/抓帧留出时间 */
lisa_ui_invoke_ui_delayed(voice_mcp_image_recognition_stop_worker, NULL, 0, 200);
});
}
static void voice_mcp_image_url_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
char *url = (char *)data;
if (!url || len == 0) {
return;
}
LISA_UI_INVOKE_UI_ARG_PTR(url, len, {
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_image_url) {
model_voice_ctx.cbs->on_image_url(model_voice_ctx.arg, _invoke_url);
}
});
}
static void notify_standby_texts_changed(void)
{
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_standby_texts_changed) {
model_voice_ctx.cbs->on_standby_texts_changed(model_voice_ctx.arg);
}
}
static void voice_cloud_show_qrcode_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_LOGI("Show qrcode message received in model_voice");
lisa_ui_invoke_ui_delayed(voice_cloud_show_qrcode_ui_worker, NULL, 0, SHOW_QRCODE_NAV_DELAY_MS);
}
static void voice_cloud_show_qrcode_ui_worker(void *arg, uint32_t arg_len)
{
(void)arg;
(void)arg_len;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_show_qrcode) {
LISA_UI_LOGI("Invoking on_show_qrcode callback");
model_voice_ctx.cbs->on_show_qrcode(model_voice_ctx.arg);
} else {
LISA_UI_LOGE("on_show_qrcode callback is NULL");
}
}
static void voice_cloud_open_info_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
(void)unused;
(void)msg_id;
(void)data;
(void)len;
(void)user_data;
LISA_UI_LOGI("Open info page message received in model_voice");
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_info_show) {
LISA_UI_LOGI("Invoking on_info_show callback");
model_voice_ctx.cbs->on_info_show(model_voice_ctx.arg);
} else {
LISA_UI_LOGE("on_info_show callback is NULL");
}
});
}
static void voice_cloud_standby_texts_received(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
char *banner_json = (char *)data;
if (!banner_json || len == 0) {
LISA_UI_LOGE("Invalid standby texts banner data");
return;
}
LISA_UI_LOGI("Standby texts banner received: %s", banner_json);
LISA_UI_INVOKE_UI_ARG_PTR(banner_json, len, {
// 解析 banner JSON 数据
cJSON *banner = cJSON_Parse(_invoke_banner_json);
if (!banner) {
LISA_UI_LOGE("Failed to parse banner JSON");
return;
}
// 获取 resources 数组
cJSON *resources = cJSON_GetObjectItem(banner, "resources");
if (!resources || !cJSON_IsArray(resources)) {
LISA_UI_LOGE("Invalid resources array");
cJSON_Delete(banner);
return;
}
// 获取 interval_ms
cJSON *interval_ms_item = cJSON_GetObjectItem(banner, "interval_ms");
uint32_t interval_ms = 3000; // 默认3秒
if (interval_ms_item && cJSON_IsNumber(interval_ms_item)) {
interval_ms = (uint32_t)interval_ms_item->valueint;
}
int resources_count = cJSON_GetArraySize(resources);
if (resources_count <= 0 || resources_count > MAX_STANDBY_TEXTS) {
LISA_UI_LOGE("Invalid resources count: %d", resources_count);
cJSON_Delete(banner);
return;
}
// 提取文本数组
int valid_count = 0;
for (int i = 0; i < resources_count && i < MAX_STANDBY_TEXTS; i++) {
cJSON *resource = cJSON_GetArrayItem(resources, i);
if (resource) {
cJSON *text_item = cJSON_GetObjectItem(resource, "text");
if (text_item && cJSON_IsString(text_item) && strlen(text_item->valuestring) > 0) {
strncpy(model_voice_ctx.standby_texts[valid_count], text_item->valuestring, MAX_TEXT_LENGTH - 1);
model_voice_ctx.standby_texts[valid_count][MAX_TEXT_LENGTH - 1] = '\0';
LISA_UI_LOGI("Found standby text %d: %s", valid_count, model_voice_ctx.standby_texts[valid_count]);
valid_count++;
}
}
}
cJSON_Delete(banner);
if (valid_count > 0) {
// 更新配置
model_voice_ctx.standby_text_count = valid_count;
model_voice_ctx.standby_interval_ms = interval_ms > 0 ? interval_ms : 3000;
model_voice_ctx.standby_enabled = true;
LISA_UI_LOGI("Successfully set %d standby texts with interval %dms", valid_count, interval_ms);
notify_standby_texts_changed();
} else {
LISA_UI_LOGE("No valid texts found");
model_voice_ctx.standby_enabled = false;
model_voice_ctx.standby_text_count = 0;
model_voice_ctx.standby_interval_ms = 0;
notify_standby_texts_changed();
}
});
}
#endif
#ifdef CONFIG_OTA
static void voice_cloud_ota_state_change(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
ota_state_t *state = (ota_state_t *)data;
LISA_UI_INVOKE_UI_ARG_PTR(state, sizeof(ota_state_t), {
if (_invoke_state->state == OTA_STATE_UP_TO_DATE) {
strncpy(model_voice_ctx.wake_word, _invoke_state->wake_word, sizeof(model_voice_ctx.wake_word) - 1);
}
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_ota_state_change) {
model_voice_ctx.cbs->on_ota_state_change(_invoke_state, model_voice_ctx.arg);
}
});
}
#endif
int model_voice_off(void)
{
model_voice_ctx.voice_en = false;
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_INVOKE_BN_ARG_NONE({
struct app_datas *app_datas = get_app_datas();
if (app_datas == NULL) {
return;
}
alarm_data_t alarm_data;
bool alarm_is_ringing = (model_alarm_get_data(&alarm_data) == 0 && alarm_data.timestamp > 0);
if (!alarm_is_ringing) {
app_datas->can_wakeup = false;
LISA_UI_LOGI("Voice off: can_wakeup set to false");
} else {
LISA_UI_LOGI("Voice off: alarm is ringing, keeping can_wakeup=%d", app_datas->can_wakeup);
}
voice_cloud_chat_stop();
});
#endif
return 0;
}
int model_voice_on(void)
{
model_voice_ctx.voice_en = true;
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_INVOKE_BN_ARG_NONE({
struct app_datas *app_datas = get_app_datas();
if (app_datas == NULL) {
return;
}
app_datas->can_wakeup = true;
});
#endif
return 0;
}
int model_voice_init(void)
{
if (model_voice_ctx.inited) {
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
struct app_datas *app_datas = get_app_datas();
if (app_datas->voice_work_mode & VOICE_WORK_MODE_BUTTON_WAKEUP) {
model_voice_ctx.wakeup_mode = MODEL_VOICE_WAKEUP_MODE_BUTTON;
} else {
if (app_datas->full_duplex) {
model_voice_ctx.wakeup_mode = MODEL_VOICE_WAKEUP_MODE_VOICE_MULTI;
} else {
model_voice_ctx.wakeup_mode = MODEL_VOICE_WAKEUP_MODE_VOICE_SINGLE;
}
}
strncpy(model_voice_ctx.prompt, app_datas->wakeup_prompt, sizeof(model_voice_ctx.prompt) - 1);
model_voice_ctx.prompt[sizeof(model_voice_ctx.prompt) - 1] = 0;
#ifdef CONFIG_OTA
model_voice_load_wake_word_from_kv();
#endif
voice_msg_sub(VOICE_MSG_CLOUD_SESSION_STARTING, voice_cloud_session_starting, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_SESSION_FINISHED, voice_cloud_session_finished, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_TTS_URL, voice_cloud_tts_url_received, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_TTS_TEXT_START, voice_cloud_tts_txt, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_TTS_TEXT_UPDATE, voice_cloud_tts_txt, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_TTS_TEXT_END, voice_cloud_tts_txt, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_IAT_START, voice_cloud_iat_txt, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_IAT_UPDATE, voice_cloud_iat_txt, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_IAT_END, voice_cloud_iat_txt, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_CONNECTED, voice_cloud_connected, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_DISCONNECTED, voice_cloud_disconnected, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_EMOJI, voice_cloud_emoji_received, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_MCP_EMOJI, voice_cloud_mcp_emoji_received, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_MCP_LOADING, voice_cloud_mcp_loading_received, NULL);
voice_msg_sub(VOICE_MSG_PLAYER_TTS_PLAYING, voice_cloud_tts_player_playing, NULL);
voice_msg_sub(VOICE_MSG_PLAYER_TTS_STOPED, voice_cloud_tts_player_stoped, NULL);
voice_msg_sub(VOICE_MSG_BUTTON_CHANGE, voice_button_changed, NULL);
voice_msg_sub(VOICE_MSG_BUTTON_IMAGE_RECOGNITION, voice_button_image_recognition, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_MCP_IMAGE_RECOGNITION, voice_mcp_image_recognition, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_MCP_IMAGE_URL, voice_mcp_image_url_received, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_STANDBY_TEXTS, voice_cloud_standby_texts_received, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_SHOW_QRCODE, voice_cloud_show_qrcode_received, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_OPEN_INFO, voice_cloud_open_info_received, NULL);
#ifdef CONFIG_OTA
voice_msg_sub(VOICE_MSG_OTA_CHECKING, voice_cloud_ota_state_change, NULL);
voice_msg_sub(VOICE_MSG_OTA_UPDATING, voice_cloud_ota_state_change, NULL);
voice_msg_sub(VOICE_MSG_OTA_UP_TO_DATE, voice_cloud_ota_state_change, NULL);
#endif
#ifdef CONFIG_VOICE_LINK_LINGXIN
model_voice_ctx.cloud_connected = (uint32_t)(app_datas->voice_cloud_connected ? 1 : 0);
#else
model_voice_ctx.cloud_connected = (uint32_t)(voice_cloud_is_connected() ? 1 : 0);
#endif
LISA_UI_LOGI("Sync cloud state on init: connected=%d", (int)model_voice_ctx.cloud_connected);
#endif
model_voice_ctx.inited = 1;
return 0;
}
int model_voice_cb_register(const struct model_voice_cb *cb, void *arg)
{
model_voice_ctx.cbs = cb;
model_voice_ctx.arg = arg;
if (model_voice_ctx.cbs && model_voice_ctx.cbs->on_wakeup_mode_changed) {
model_voice_ctx.cbs->on_wakeup_mode_changed(model_voice_ctx.arg, model_voice_ctx.wakeup_mode);
}
return 0;
}
int model_voice_cb_unregister(const struct model_voice_cb *cb)
{
model_voice_ctx.cbs = NULL;
return 0;
}
uint8_t model_voice_cloud_is_connected(void)
{
return model_voice_ctx.cloud_connected;
}
uint8_t model_voice_cloud_is_running(void)
{
return model_voice_ctx.cloud_connected & model_voice_ctx.running;
}
uint8_t model_voice_tts_is_playing(void)
{
return (uint8_t)model_voice_ctx.tts_playing;
}
const char *model_voice_last_iat_text_get(void)
{
return s_last_iat_text;
}
uint8_t model_voice_img_rec_is_mcp(void)
{
return (uint8_t)(model_voice_ctx.img_rec_mode == IMG_REC_MODE_MCP);
}
uint32_t model_voice_standby_text_count_get(void)
{
if (!model_voice_ctx.standby_enabled) {
return 0;
}
return model_voice_ctx.standby_text_count;
}
uint32_t model_voice_standby_text_interval_ms_get(void)
{
if (!model_voice_ctx.standby_enabled) {
return 0;
}
return model_voice_ctx.standby_interval_ms;
}
const char *model_voice_standby_text_get(uint32_t index)
{
if (!model_voice_ctx.standby_enabled) {
return NULL;
}
if (index >= model_voice_ctx.standby_text_count) {
return NULL;
}
#ifdef CONFIG_OTA
return make_wake_hint_text(model_voice_ctx.standby_texts[index]);
#else
return model_voice_ctx.standby_texts[index];
#endif
}
#ifdef LISA_UI_PLATFORM_ARCS
struct voice_img_upload_msg {
uint8_t *data;
uint32_t len;
uint32_t w;
uint32_t h;
uint8_t mode;
uint8_t mcp_id[64];
};
struct voice_img_cloud_sync_msg {
uint8_t *jpg_img;
uint32_t len;
};
static void voice_img_rec(void *data, uint32_t len, struct voice_invoke_rsp *rsp)
{
struct voice_img_cloud_sync_msg *msg = (struct voice_img_cloud_sync_msg *)data;
int r = voice_cloud_image_recognition(msg->jpg_img, msg->len);
rsp->err = r;
}
static cJSON *mcp_image_result_create(const char *url)
{
cJSON *result = cJSON_CreateObject();
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
if (!content_array) {
cJSON_Delete(result);
return NULL;
}
cJSON *content_item = cJSON_CreateObject();
if (!content_item) {
cJSON_Delete(content_array);
cJSON_Delete(result);
return NULL;
}
cJSON_AddStringToObject(content_item, "type", "image");
if (url && url[0] != '\0') {
cJSON_AddStringToObject(content_item, "data", url);
} else {
cJSON_AddStringToObject(content_item, "data", "");
}
cJSON_AddStringToObject(content_item, "mimeType", "url");
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
if (url && url[0] != '\0') {
cJSON_AddBoolToObject(result, "isError", false);
} else {
cJSON_AddBoolToObject(result, "isError", true);
}
return result;
}
static void async_task_img_upload(void *p, bool *should_stop)
{
struct voice_img_upload_msg *msg = (struct voice_img_upload_msg *)p;
uint8_t *jpeg_data = NULL;
uint32_t jpeg_len = 0;
int ret = img_helper_rgb565_to_jpeg(msg->data, msg->len, msg->w, msg->h, &jpeg_data, &jpeg_len);
if (ret != 0 || !jpeg_data) {
lisa_ui_free(msg->data);
lisa_ui_free(msg);
LISA_UI_LOGE("Failed to encode JPEG: %d", ret);
return;
}
if (msg->mode == IMG_REC_MODE_LSCHAT) {
struct voice_invoke_rsp rsp = {
.err = -1,
};
struct voice_img_cloud_sync_msg sync_msg = {
.jpg_img = jpeg_data,
.len = jpeg_len,
};
int r = voice_invoke_sync(voice_img_rec, &sync_msg, sizeof(struct voice_img_cloud_sync_msg),
(struct voice_invoke_rsp *)&rsp, 1000);
} else if (msg->mode == IMG_REC_MODE_MCP) {
char *url = NULL;
int upload_ret = voice_cloud_upload_jpeg_img(jpeg_data, jpeg_len, &url);
cJSON *result = mcp_image_result_create(url);
if (result) {
if (url && upload_ret == 0) {
LISA_UI_LOGI("jpeg upload success, url: %s", url);
} else {
LISA_UI_LOGE("jpeg upload failed");
}
mcp_tool_call_result_response(msg->mcp_id, result);
cJSON_Delete(result);
}
if (url) {
voice_cloud_jpeg_img_url_free(url);
}
}
img_helper_jpeg_free(jpeg_data);
lisa_ui_free(msg->data);
lisa_ui_free(msg);
}
#endif
int model_voice_img_recognition(uint8_t *rgb565, uint32_t len, int width, int height)
{
#ifdef LISA_UI_PLATFORM_ARCS
uint8_t *rgb565_cpy = lisa_ui_malloc(len);
if (rgb565_cpy == NULL) {
return -3;
}
memcpy(rgb565_cpy, rgb565, len);
struct voice_img_upload_msg *msg = lisa_ui_malloc(sizeof(struct voice_img_upload_msg));
if (msg == NULL) {
lisa_ui_free(rgb565_cpy);
return -1;
}
msg->data = rgb565_cpy;
msg->len = len;
msg->w = width;
msg->h = height;
msg->mode = model_voice_ctx.img_rec_mode;
memcpy(msg->mcp_id, model_voice_ctx.mcp_id, sizeof(msg->mcp_id));
async_task_t *async_task = async_task_create("img_rec", 4096, 5, async_task_img_upload, NULL, msg);
if (async_task == NULL) {
lisa_ui_free(rgb565_cpy);
return -1;
}
async_task_start(async_task);
return 0;
#else
return -5;
#endif
}

View File

@@ -0,0 +1,69 @@
#ifndef __MODEL_VOICE_H__
#define __MODEL_VOICE_H__
#include <math.h>
#include <stdbool.h>
#ifdef CONFIG_OTA
#include "ota_manager.h"
#endif
typedef enum {
MODEL_VOICE_WAKEUP_MODE_BUTTON = 0,
MODEL_VOICE_WAKEUP_MODE_VOICE_SINGLE,
MODEL_VOICE_WAKEUP_MODE_VOICE_MULTI,
MODEL_VOICE_WAKEUP_MODE_MAX
} model_voice_wakeup_mode_t;
int model_voice_off(void);
int model_voice_on(void);
struct model_voice_cb {
void (*on_tts_stoped)(void *arg);
void (*on_tts_playing)(void *arg);
void (*on_emoji)(void *arg, const char *emoji_name);
void (*on_mcp_emoji)(void *arg, const char *emoji_name);
void (*on_mcp_loading)(void *arg, bool is_loading, const char *loading_text);
void (*on_connected)(void *arg);
void (*on_disconnected)(void *arg);
void (*on_start)(void *arg);
void (*on_finished)(void *arg);
void (*on_tts_text_start)(void *arg);
void (*on_tts_text_update)(const char *text, void *arg);
void (*on_tts_text_end)(void *arg);
void (*on_iat_text_start)(void *arg);
void (*on_iat_text_update)(const char *text, void *arg);
void (*on_iat_text_end)(void *arg);
void (*on_image_rec)(void *arg);
void (*on_image_preview)(void *arg);
void (*on_image_url)(void *arg, const char *url);
void (*on_info_show)(void *arg);
void (*on_show_qrcode)(void *arg);
void (*on_standby_texts_changed)(void *arg);
void (*on_standby_text_update)(void *arg, const char *text, bool is_cloud_text);
#ifdef CONFIG_OTA
void (*on_ota_state_change)(const ota_state_t *state, void *arg);
#endif
void (*on_wakeup_mode_changed)(void *arg, model_voice_wakeup_mode_t mode);
};
int model_voice_init(void);
int model_voice_cb_register(const struct model_voice_cb *cb, void *arg);
int model_voice_cb_unregister(const struct model_voice_cb *cb);
const char *model_voice_role_name_get(void);
const char *model_voice_role_propmt_get(void);
uint8_t model_voice_cloud_is_connected(void);
uint8_t model_voice_cloud_is_running(void);
uint8_t model_voice_tts_is_playing(void);
const char *model_voice_last_iat_text_get(void);
model_voice_wakeup_mode_t model_voice_wakeup_mode_get(void);
int model_voice_wakeup_mode_set(model_voice_wakeup_mode_t mode);
const char *model_voice_wakeup_mode_name_get(model_voice_wakeup_mode_t mode);
int model_voice_img_recognition(uint8_t *rgb565, uint32_t len, int width, int height);
uint32_t model_voice_standby_text_count_get(void);
uint32_t model_voice_standby_text_interval_ms_get(void);
const char *model_voice_standby_text_get(uint32_t index);
#endif

View File

@@ -0,0 +1,436 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#define TAG "model_wifi"
#include "lisa_ui_invoke.h"
#include "lisa_ui_log.h"
#include "model_wifi.h"
#ifdef LISA_UI_PLATFORM_ARCS
#include "wifi_manager/wifi_manager.h"
#include "voice_msg.h"
#endif
struct model_wifi_context {
uint32_t inited: 1;
uint32_t scanning: 1;
const struct model_wifi_cb *cbs;
void *arg;
model_wifi_status_t status;
model_wifi_scan_info_t scan_list[WIFI_MAX_SCAN_APS];
int scan_count;
};
static struct model_wifi_context model_wifi_ctx = {
.inited = 0,
.scanning = 0,
.status = MODEL_WIFI_STATUS_DISCONNECTED,
.scan_count = 0,
};
#ifdef LISA_UI_PLATFORM_ARCS
// WiFi Manager 到 Model 的加密模式转换
static model_wifi_encryption_mode_t convert_encryption_mode(wifi_mgr_wifi_encryption_mode_t mode)
{
switch (mode) {
case WIFI_MGR_WIFI_AUTH_AUTO:
return MODEL_WIFI_AUTH_AUTO;
case WIFI_MGR_WIFI_AUTH_OPEN:
return MODEL_WIFI_AUTH_OPEN;
case WIFI_MGR_WIFI_AUTH_WEP:
return MODEL_WIFI_AUTH_WEP;
case WIFI_MGR_WIFI_AUTH_WPA_PSK:
return MODEL_WIFI_AUTH_WPA_PSK;
case WIFI_MGR_WIFI_AUTH_WPA2_PSK:
return MODEL_WIFI_AUTH_WPA2_PSK;
case WIFI_MGR_WIFI_AUTH_WPA_WPA2_PSK:
return MODEL_WIFI_AUTH_WPA_WPA2_PSK;
case WIFI_MGR_WIFI_AUTH_WPA2_ENTERPRISE:
return MODEL_WIFI_AUTH_WPA2_ENTERPRISE;
case WIFI_MGR_WIFI_AUTH_WPA3_PSK:
return MODEL_WIFI_AUTH_WPA3_PSK;
case WIFI_MGR_WIFI_AUTH_WPA2_WPA3_PSK:
return MODEL_WIFI_AUTH_WPA2_WPA3_PSK;
default:
return MODEL_WIFI_AUTH_UNKNOWN;
}
}
// Model 到 WiFi Manager 的加密模式转换
static wifi_mgr_wifi_encryption_mode_t convert_to_wifi_mgr_encryption_mode(model_wifi_encryption_mode_t mode)
{
switch (mode) {
case MODEL_WIFI_AUTH_AUTO:
return WIFI_MGR_WIFI_AUTH_AUTO;
case MODEL_WIFI_AUTH_OPEN:
return WIFI_MGR_WIFI_AUTH_OPEN;
case MODEL_WIFI_AUTH_WEP:
return WIFI_MGR_WIFI_AUTH_WEP;
case MODEL_WIFI_AUTH_WPA_PSK:
return WIFI_MGR_WIFI_AUTH_WPA_PSK;
case MODEL_WIFI_AUTH_WPA2_PSK:
return WIFI_MGR_WIFI_AUTH_WPA2_PSK;
case MODEL_WIFI_AUTH_WPA_WPA2_PSK:
return WIFI_MGR_WIFI_AUTH_WPA_WPA2_PSK;
case MODEL_WIFI_AUTH_WPA2_ENTERPRISE:
return WIFI_MGR_WIFI_AUTH_WPA2_ENTERPRISE;
case MODEL_WIFI_AUTH_WPA3_PSK:
return WIFI_MGR_WIFI_AUTH_WPA3_PSK;
case MODEL_WIFI_AUTH_WPA2_WPA3_PSK:
return WIFI_MGR_WIFI_AUTH_WPA2_WPA3_PSK;
default:
return WIFI_MGR_WIFI_AUTH_UNKNOWN;
}
}
// WiFi Manager 到 Model 的状态转换
static model_wifi_status_t convert_wifi_status(wifi_mgr_connection_status_t status)
{
switch (status) {
case WIFI_MGR_STA_CONNECTED:
return MODEL_WIFI_STATUS_CONNECTED;
case WIFI_MGR_STA_CONNECTING:
return MODEL_WIFI_STATUS_CONNECTING;
case WIFI_MGR_STA_DISCONNECTED:
return MODEL_WIFI_STATUS_DISCONNECTED;
default:
return MODEL_WIFI_STATUS_UNKNOWN;
}
}
// WiFi 连接状态回调
static void wifi_connection_event_handler(wifi_mgr_connection_info_t *connection_info, void *arg)
{
if (!connection_info) {
return;
}
LISA_UI_LOGI("wifi connecting info, ssid: %s, reason: %d", connection_info->sta_info->ssid,
connection_info->reason);
wifi_mgr_connection_status_t status = connection_info->status;
int reason = connection_info->reason;
LISA_UI_INVOKE_UI_ARG_BASE(status, {
model_wifi_ctx.status = convert_wifi_status(_invoke_status);
if (model_wifi_ctx.cbs) {
switch (_invoke_status) {
case WIFI_MGR_STA_CONNECTED:
if (model_wifi_ctx.cbs->on_connected) {
model_wifi_sta_config_t sta_info;
if (model_wifi_get_connected_info(&sta_info) == 0) {
model_wifi_ctx.cbs->on_connected(&sta_info, model_wifi_ctx.arg);
}
}
break;
case WIFI_MGR_STA_CONNECTING:
if (model_wifi_ctx.cbs->on_connecting) {
model_wifi_ctx.cbs->on_connecting(model_wifi_ctx.arg);
}
break;
case WIFI_MGR_STA_DISCONNECTED:
if (model_wifi_ctx.cbs->on_disconnected) {
model_wifi_ctx.cbs->on_disconnected(0, model_wifi_ctx.arg);
}
break;
default:
break;
}
}
});
}
// WiFi 扫描完成回调
static void wifi_scan_done_handler(wifi_mgr_scan_info_t *aps_info, int ap_num, void *arg)
{
if (ap_num > 0 && aps_info) {
// 计算需要拷贝的AP数量
int copy_count = ap_num > WIFI_MAX_SCAN_APS ? WIFI_MAX_SCAN_APS : ap_num;
uint32_t data_len = sizeof(wifi_mgr_scan_info_t) * copy_count;
LISA_UI_INVOKE_UI_ARG_PTR(aps_info, data_len, {
wifi_mgr_scan_info_t *scan_result = (wifi_mgr_scan_info_t *)_invoke_aps_info;
int count = _invoke_len / sizeof(wifi_mgr_scan_info_t);
model_wifi_ctx.scanning = 0;
model_wifi_ctx.scan_count = count;
// 转换扫描结果到 model 层数据结构
for (int i = 0; i < count; i++) {
strncpy(model_wifi_ctx.scan_list[i].ssid, scan_result[i].ssid, WIFI_SSID_MAX_LEN - 1);
model_wifi_ctx.scan_list[i].ssid[WIFI_SSID_MAX_LEN - 1] = '\0';
strncpy(model_wifi_ctx.scan_list[i].bssid, scan_result[i].bssid, WIFI_BSSID_MAX_LEN - 1);
model_wifi_ctx.scan_list[i].bssid[WIFI_BSSID_MAX_LEN - 1] = '\0';
model_wifi_ctx.scan_list[i].channel = scan_result[i].channel;
model_wifi_ctx.scan_list[i].rssi = scan_result[i].rssi;
model_wifi_ctx.scan_list[i].encryption_mode = convert_encryption_mode(scan_result[i].encryption_mode);
}
if (model_wifi_ctx.cbs && model_wifi_ctx.cbs->on_scan_done) {
model_wifi_ctx.cbs->on_scan_done(model_wifi_ctx.scan_list, count, model_wifi_ctx.arg);
}
});
} else {
// 扫描失败
int num = ap_num;
LISA_UI_INVOKE_UI_ARG_BASE(num, {
model_wifi_ctx.scanning = 0;
model_wifi_ctx.scan_count = 0;
if (model_wifi_ctx.cbs && model_wifi_ctx.cbs->on_scan_failed) {
model_wifi_ctx.cbs->on_scan_failed(_invoke_num, model_wifi_ctx.arg);
}
});
}
}
static void wifi_disconnected_msg_handle(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_wifi_ctx.status = MODEL_WIFI_STATUS_DISCONNECTED;
});
}
static void wifi_ip_got_msg_handle(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_UI_INVOKE_UI_ARG_NONE({
model_wifi_ctx.status = MODEL_WIFI_STATUS_CONNECTED;
});
}
#endif // LISA_UI_PLATFORM_ARCS
int model_wifi_init(void)
{
if (model_wifi_ctx.inited) {
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
int ret;
ret = wifi_mgr_sta_add_connection_cb(wifi_connection_event_handler, NULL);
if (ret != 0) {
LISA_UI_LOGE("Failed to add connection callback: %d", ret);
return ret;
}
ret = wifi_mgr_add_scan_done_cb(wifi_scan_done_handler, NULL);
if (ret != 0) {
LISA_UI_LOGE("Failed to add scan done callback: %d", ret);
wifi_mgr_sta_remove_connection_cb(wifi_connection_event_handler);
return ret;
}
wifi_mgr_connection_status_t status = wifi_mgr_sta_get_status();
model_wifi_ctx.status = convert_wifi_status(status);
LISA_UI_LOGD("WiFi model initialized, status: %d", model_wifi_ctx.status);
voice_msg_sub(VOICE_MSG_WIFI_DISCONNECTED, wifi_disconnected_msg_handle, NULL);
voice_msg_sub(VOICE_MSG_WIFI_IP_GOT, wifi_ip_got_msg_handle, NULL);
#endif
model_wifi_ctx.inited = 1;
return 0;
}
int model_wifi_deinit(void)
{
if (!model_wifi_ctx.inited) {
return 0;
}
#ifdef LISA_UI_PLATFORM_ARCS
wifi_mgr_sta_remove_connection_cb(wifi_connection_event_handler);
wifi_mgr_remove_scan_done_cb(wifi_scan_done_handler);
voice_msg_unsub(VOICE_MSG_WIFI_DISCONNECTED, wifi_disconnected_msg_handle);
#endif
model_wifi_ctx.inited = 0;
model_wifi_ctx.cbs = NULL;
model_wifi_ctx.arg = NULL;
return 0;
}
int model_wifi_cb_register(const struct model_wifi_cb *cb, void *arg)
{
model_wifi_ctx.cbs = cb;
model_wifi_ctx.arg = arg;
return 0;
}
int model_wifi_cb_unregister(const struct model_wifi_cb *cb)
{
model_wifi_ctx.cbs = NULL;
model_wifi_ctx.arg = NULL;
return 0;
}
int model_wifi_scan_start(void)
{
if (!model_wifi_ctx.inited) {
LISA_UI_LOGE("WiFi model not initialized");
return -1;
}
// if (model_wifi_ctx.scanning) {
// LISA_UI_LOGW("WiFi scan already in progress");
// return -2;
// }
#ifdef LISA_UI_PLATFORM_ARCS
model_wifi_ctx.scanning = 1;
LISA_UI_INVOKE_BN_ARG_NONE({
wifi_mgr_scan_info_t ap_info[WIFI_MAX_SCAN_APS];
int ret = wifi_mgr_scan_ap(ap_info, WIFI_MAX_SCAN_APS, false);
// 扫描结果会通过回调返回
if (ret < 0) {
LISA_UI_LOGE("WiFi scan failed: %d", ret);
}
});
#else
LISA_UI_LOGW("WiFi scan not supported on this platform");
return -3;
#endif
return 0;
}
model_wifi_status_t model_wifi_get_status(void)
{
return model_wifi_ctx.status;
}
int model_wifi_connect(const char *ssid, const char *pwd, const char *bssid)
{
if (!model_wifi_ctx.inited) {
LISA_UI_LOGE("WiFi model not initialized");
return -1;
}
if (!ssid) {
LISA_UI_LOGE("SSID cannot be NULL");
return -2;
}
#ifdef LISA_UI_PLATFORM_ARCS
wifi_mgr_sta_config_t sta_config;
memset(&sta_config, 0, sizeof(sta_config));
strncpy(sta_config.ssid, ssid, sizeof(sta_config.ssid) - 1);
sta_config.ssid[sizeof(sta_config.ssid) - 1] = '\0';
if (pwd) {
strncpy(sta_config.pwd, pwd, sizeof(sta_config.pwd) - 1);
sta_config.pwd[sizeof(sta_config.pwd) - 1] = '\0';
}
if (bssid) {
strncpy(sta_config.bssid, bssid, sizeof(sta_config.bssid) - 1);
sta_config.bssid[sizeof(sta_config.bssid) - 1] = '\0';
}
sta_config.encryption_mode = WIFI_MGR_WIFI_AUTH_AUTO;
wifi_mgr_sta_config_t *config = &sta_config;
LISA_UI_INVOKE_BN_ARG_PTR(config, sizeof(sta_config), {
wifi_mgr_sta_config_t *cfg = (wifi_mgr_sta_config_t *)_invoke_config;
int ret = wifi_mgr_sta_connect(cfg, false);
if (ret != 0) {
LISA_UI_INVOKE_UI_ARG_NONE({
if (model_wifi_ctx.cbs && model_wifi_ctx.cbs->on_connection_failed) {
model_wifi_ctx.cbs->on_connection_failed(-1, model_wifi_ctx.arg);
}
});
}
});
#else
LISA_UI_LOGW("WiFi connect not supported on this platform");
return -3;
#endif
return 0;
}
int model_wifi_disconnect(void)
{
if (!model_wifi_ctx.inited) {
LISA_UI_LOGE("WiFi model not initialized");
return -1;
}
#ifdef LISA_UI_PLATFORM_ARCS
LISA_UI_INVOKE_BN_ARG_NONE({
int ret = wifi_mgr_sta_disconnect(false);
if (ret != 0) {
LISA_UI_LOGE("WiFi disconnect failed: %d", ret);
} else {
LISA_UI_LOGD("WiFi disconnected");
}
});
#else
LISA_UI_LOGW("WiFi disconnect not supported on this platform");
return -2;
#endif
return 0;
}
int model_wifi_get_connected_info(model_wifi_sta_config_t *sta_info)
{
if (!model_wifi_ctx.inited) {
LISA_UI_LOGE("WiFi model not initialized");
return -1;
}
if (!sta_info) {
LISA_UI_LOGE("sta_info cannot be NULL");
return -2;
}
#ifdef LISA_UI_PLATFORM_ARCS
wifi_mgr_sta_config_t wifi_sta_info;
int ret = wifi_mgr_sta_get_connected_info(&wifi_sta_info);
if (ret != 0) {
LISA_UI_LOGE("Failed to get WiFi connected info: %d", ret);
return ret;
}
// 转换数据结构
strncpy(sta_info->ssid, wifi_sta_info.ssid, WIFI_SSID_MAX_LEN - 1);
sta_info->ssid[WIFI_SSID_MAX_LEN - 1] = '\0';
strncpy(sta_info->bssid, wifi_sta_info.bssid, WIFI_BSSID_MAX_LEN - 1);
sta_info->bssid[WIFI_BSSID_MAX_LEN - 1] = '\0';
strncpy(sta_info->pwd, wifi_sta_info.pwd, WIFI_PWD_MAX_LEN - 1);
sta_info->pwd[WIFI_PWD_MAX_LEN - 1] = '\0';
sta_info->channel = wifi_sta_info.channel;
sta_info->rssi = wifi_sta_info.rssi;
sta_info->encryption_mode = convert_encryption_mode(wifi_sta_info.encryption_mode);
return 0;
#else
LISA_UI_LOGW("Get WiFi connected info not supported on this platform");
return -3;
#endif
}

View File

@@ -0,0 +1,73 @@
#ifndef __MODEL_WIFI_H__
#define __MODEL_WIFI_H__
#include <stdint.h>
#include <stdbool.h>
#define WIFI_SSID_MAX_LEN 32
#define WIFI_BSSID_MAX_LEN 18
#define WIFI_PWD_MAX_LEN 64
#define WIFI_MAX_SCAN_APS 20
typedef enum {
MODEL_WIFI_AUTH_AUTO = 0,
MODEL_WIFI_AUTH_OPEN,
MODEL_WIFI_AUTH_WEP,
MODEL_WIFI_AUTH_WPA_PSK,
MODEL_WIFI_AUTH_WPA2_PSK,
MODEL_WIFI_AUTH_WPA_WPA2_PSK,
MODEL_WIFI_AUTH_WPA2_ENTERPRISE,
MODEL_WIFI_AUTH_WPA3_PSK,
MODEL_WIFI_AUTH_WPA2_WPA3_PSK,
MODEL_WIFI_AUTH_UNKNOWN,
MODEL_WIFI_AUTH_MAX,
} model_wifi_encryption_mode_t;
typedef enum {
MODEL_WIFI_STATUS_CONNECTED = 0,
MODEL_WIFI_STATUS_CONNECTING,
MODEL_WIFI_STATUS_DISCONNECTED,
MODEL_WIFI_STATUS_UNKNOWN,
} model_wifi_status_t;
typedef struct {
char ssid[WIFI_SSID_MAX_LEN];
char bssid[WIFI_BSSID_MAX_LEN];
char pwd[WIFI_PWD_MAX_LEN];
int channel;
int rssi;
model_wifi_encryption_mode_t encryption_mode;
} model_wifi_sta_config_t;
typedef struct {
char ssid[WIFI_SSID_MAX_LEN];
char bssid[WIFI_BSSID_MAX_LEN];
int channel;
int rssi;
model_wifi_encryption_mode_t encryption_mode;
} model_wifi_scan_info_t;
struct model_wifi_cb {
void (*on_connected)(model_wifi_sta_config_t *sta_info, void *arg);
void (*on_disconnected)(int reason, void *arg);
void (*on_connecting)(void *arg);
void (*on_connection_failed)(int reason, void *arg);
void (*on_scan_done)(model_wifi_scan_info_t *aps_info, int ap_num, void *arg);
void (*on_scan_failed)(int reason, void *arg);
};
int model_wifi_init(void);
int model_wifi_deinit(void);
int model_wifi_cb_register(const struct model_wifi_cb *cb, void *arg);
int model_wifi_cb_unregister(const struct model_wifi_cb *cb);
int model_wifi_scan_start(void);
model_wifi_status_t model_wifi_get_status(void);
int model_wifi_connect(const char *ssid, const char *pwd, const char *bssid);
int model_wifi_disconnect(void);
int model_wifi_get_connected_info(model_wifi_sta_config_t *sta_info);
#endif