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,52 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# 设置默认板型 (如果未在命令行指定)
if(NOT DEFINED BOARD)
set(BOARD "arcs_mini")
message(STATUS "Using default BOARD: ${BOARD}")
endif()
find_package(listenai-cmake REQUIRED HINTS $ENV{ARCS_BASE})
project(arcs-mini)
include(version.cmake)
add_definitions(-DLISTEN_CLOUD -DCONFIG_EXT_RAM)
# Add project root directory to include path for app-config-tls-generic.h and memap.h
listenai_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# Get project root directory (two levels up from apps/aopu-yuba)
get_filename_component(PROJECT_TOP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../.. ABSOLUTE)
message (STATUS "PROJECT_TOP_DIR: ${PROJECT_TOP_DIR}")
add_subdirectory(${PROJECT_TOP_DIR}/modules ${CMAKE_BINARY_DIR}/modules)
add_subdirectory(${PROJECT_TOP_DIR}/src ${CMAKE_BINARY_DIR}/src)
if (DEFINED CONFIG_APPLICATION_UI)
add_subdirectory(${PROJECT_TOP_DIR}/apps-ui ${CMAKE_BINARY_DIR}/apps-ui)
endif()
add_subdirectory(tone)
add_subdirectory(services)
add_subdirectory(mcp-tools)
add_subdirectory(misc)
listenai_add_executable(${PROJECT_NAME})
# add_subdirectory(msgs)
# add_subdirectory(recognize)
target_sources(
${PROJECT_NAME} PRIVATE
main.c
voice_player.c
)
target_link_options(${PROJECT_NAME} PRIVATE
-Wl,--wrap,gettimeofday
-Wl,--wrap,settimeofday
-Wl,--wrap,time
-Wl,--wrap,gmtime_r
)

9
apps/arcs-mini/Kconfig Normal file
View File

@@ -0,0 +1,9 @@
osource "$ARCS_BASE/Kconfig"
menu "Voice Application"
rsource "../../modules/Kconfig"
rsource "../../src/Kconfig"
orsource "../../apps-ui/Kconfig"
endmenu

282
apps/arcs-mini/main.c Normal file
View File

@@ -0,0 +1,282 @@
#include "FreeRTOS.h"
#include "task.h"
#include "project_version.h"
#define TAG "main"
#include "lisa_log.h"
#include "lisa_time.h"
#include "alarm_aiui.h"
#include "service_led.h"
#include "service_volume.h"
#include "service_brightness.h"
#include "service_button.h"
#include "service_camera.h"
#include "service_alarm.h"
#include "service_image.h"
#include "voice_msg.h"
#include "lisa_kv.h"
#include "wifi_manager/wifi_manager.h"
#include "app_datas.h"
#include "tone.h"
#include "player_mgr.h"
#include "power/power_manager.h"
#include "pa_manager.h"
#include "lisa_display.h"
#include "battery/battery.h"
#include "lisa_ui_nav_scr.h"
#include "apps/llm/models/model_voice.h"
#include "voice_cloud.h"
#ifdef CONFIG_VOICE_LINK_LINGXIN
#include "lingxin_server_config.h"
#endif
#ifdef CONFIG_VOICE_LINK_LINGXIN
#define LINGXIN_BUTTON_WAKEUP_DEBOUNCE_MS 800U
static bool lingxin_button_wakeup_debounce(void)
{
static uint32_t s_last_wakeup_ms;
uint32_t now_ms = (uint32_t)lisa_os_get_tick_ms();
if (s_last_wakeup_ms != 0 && (uint32_t)(now_ms - s_last_wakeup_ms) < LINGXIN_BUTTON_WAKEUP_DEBOUNCE_MS) {
LISA_LOGI(TAG, "[Lingxin] single click ignored by debounce");
return false;
}
s_last_wakeup_ms = now_ms;
return true;
}
static void lingxin_publish_button_wakeup(void)
{
struct app_datas *app_datas = get_app_datas();
if (app_datas && (app_datas->voice_work_mode & VOICE_WORK_MODE_BUTTON_WAKEUP)) {
voice_msg_pub(VOICE_MSG_WAKEUP_BUTTON_START, NULL, 0);
} else {
const char keyword[] = "xiao ling xiao ling";
voice_msg_pub(VOICE_MSG_WAKEUP_KEYWORD, (void *)keyword, sizeof(keyword));
}
}
#endif
extern uint8_t app_ble_adv_start(uint8_t adv_id, uint8_t adv_type);
void network_reset(void)
{
LOGI("network reset running...");
#if CONFIG_WIFI_MANAGER
wifi_mgr_sta_disconnect(true);
wifi_mgr_sta_config_t list[16] = {0};
int count = 0;
do {
count = wifi_mgr_storage_search_ap(list, (int)(sizeof(list) / sizeof(list[0])), SEARCH_ALL, NULL);
if (count > 0) {
int del_count = count;
if (del_count > (int)(sizeof(list) / sizeof(list[0]))) {
del_count = (int)(sizeof(list) / sizeof(list[0]));
}
for (int i = 0; i < del_count; i++) {
wifi_mgr_storage_delete_ap(&list[i]);
}
}
} while (count > (int)(sizeof(list) / sizeof(list[0])));
#endif
}
void factory_reset(void)
{
LOGI("factory reset running...");
lisa_kv_clear();
network_reset();
LOGI("factory reset dwone.");
LOGI("The system will restart after 300ms.");
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_103), 0);
vTaskDelay(2000);
extern void sys_platform_sw_full_reset(void);
sys_platform_sw_full_reset();
}
static void voice_system_network_probe_success(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
#ifdef CONFIG_VOICE_LINK_LINGXIN
if (lingxin_server_config_fetch() != 0) {
LOGE("[Lingxin] failed to load runtime config");
}
#endif
service_alarm_init();
}
static void 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;
service_image_waiting_cancel();
if (evt->button_id != 0) {
return;
}
switch (evt->action) {
case VOICE_MSG_BUTTON_ACTION_CLICK:
{
/* 单击:非主页先回主页;主页则触发按键唤醒 */
if (lisa_ui_nav_scr_get_top_id() != 0) {
LISA_LOGI(TAG, "Single click: not on home page, navigating home");
lisa_ui_nav_scr_nav_to(0);
break;
} else {
LISA_LOGI(TAG, "Single click: wakeup trigger");
#ifdef CONFIG_VOICE_LINK_LINGXIN
if (!lingxin_button_wakeup_debounce()) {
break;
}
LISA_LOGI(TAG, "[Lingxin] single click: wakeup/barge-in trigger");
lingxin_publish_button_wakeup();
#else
if (model_voice_tts_is_playing()) {
LISA_LOGI(TAG, "Single click: TTS playing, stop it");
player_mgr_stop(TTS);
break;
}
#ifdef CONFIG_VOICE_LINK_LSCHAT
if (model_voice_cloud_is_running()) {
voice_msg_pub(VOICE_MSG_CLOUD_MCP_CHAT_EXIT, NULL, 0);
} else {
const char keyword[] = "xiao ling xiao ling";
voice_msg_pub(VOICE_MSG_WAKEUP_KEYWORD, (void *)keyword, sizeof(keyword));
}
#else
LISA_LOGI(TAG, "LSChat link inactive, ignore LSChat wakeup trigger");
#endif
#endif
}
break;
}
case VOICE_MSG_BUTTON_ACTION_DOUBLE_CLICK:
{
/* 双击:留给 LiveKit 设备呼叫,避免同时触发拍照识图 */
LISA_LOGI(TAG, "power button double click, reserved for livekit call");
break;
}
case VOICE_MSG_BUTTON_ACTION_TRIPLE_CLICK:
{
/* 三击:留给 LiveKit 来电拒接,不再打开二维码/信息页 */
LISA_LOGI(TAG, "power button triple click, reserved for livekit reject");
break;
}
case VOICE_MSG_BUTTON_ACTION_QUADRUPLE_CLICK:
{
/* 四击:拍照识图 */
LISA_LOGI(TAG, "power button quadruple click, image recognition");
voice_msg_pub(VOICE_MSG_BUTTON_IMAGE_RECOGNITION, NULL, 0);
break;
}
case VOICE_MSG_BUTTON_ACTION_QUINTUPLE_CLICK:
case VOICE_MSG_BUTTON_ACTION_SEXTUPLE_CLICK:
case VOICE_MSG_BUTTON_ACTION_SEPTUPLE_CLICK:
{
struct app_datas *app_datas = get_app_datas();
/* 连击 >=5进入 BLE 配网并打开信息页 */
LISA_LOGI(TAG, "power button multi click, enter BLE config");
if (app_datas != NULL && !app_datas->wifi_connected) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_70), 0);
}
vTaskDelay(pdMS_TO_TICKS(1000));
network_reset();
app_ble_adv_start(0, 1);
voice_msg_pub(VOICE_MSG_CLOUD_OPEN_INFO, NULL, 0);
break;
}
case VOICE_MSG_BUTTON_ACTION_REPEAT_CLICK:
{
/* 连击 >=8恢复出厂 */
LISA_LOGI(TAG, "power button repeat click, factory reset");
factory_reset();
break;
}
case VOICE_MSG_BUTTON_ACTION_LONG_HOLD:
{
/* 长按关机USB 供电时忽略)*/
if (power_is_usb_plugged()) {
LISA_LOGI(TAG, "USB connected, ignore long press shutdown");
} else {
LISA_LOGI(TAG, "power button long press hold, shutting down...");
power_shutdown();
}
break;
}
}
}
static void shutdown(void)
{
LISA_LOGI(TAG, "System shutting down...");
pa_manager_onoff(0);
lisa_device_t *disp = lisa_device_get("display");
lisa_display_blanking_on(disp);
lisa_display_set_brightness(disp, 0);
vTaskDelay(pdMS_TO_TICKS(500));
}
int main(int argc, char **argv)
{
LOGI("Application version: %s-%s", PROJECT_VERSION_STR, PROJECT_VERSION_COMMIT);
boot_watchdog_feed();
power_config_t power_cfg = {
.on_shutdown = shutdown,
};
power_init(&power_cfg);
if (!power_wait_settle()) {
power_shutdown();
return 0;
}
battery_init();
voice_msg_sub(VOICE_MSG_SYSTEM_NETWORK_PROBE_SUCCESS, voice_system_network_probe_success, NULL);
voice_msg_sub(VOICE_MSG_BUTTON_CHANGE, button_changed, NULL);
service_led_init();
service_volume_init();
service_brightness_init();
service_button_init();
service_image_init();
#if !CONFIG_4G_MODULE
service_camera_init();
#endif
#if CONFIG_APPLICATION_UI
extern int lisa_ui_init(void);
lisa_ui_init();
#endif
#if CONFIG_WIFI_MANAGER
wifi_mgr_sta_config_t list[8] = {0};
int count = wifi_mgr_storage_search_ap(list, (int)(sizeof(list) / sizeof(list[0])), SEARCH_ALL, NULL);
if (count == 0) {
voice_msg_pub(VOICE_MSG_CLOUD_OPEN_INFO, NULL, 0);
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_70), 0);
}
#endif
vTaskDelay(pdMS_TO_TICKS(3000));
while (1) {
boot_watchdog_feed();
vTaskDelay(pdMS_TO_TICKS(100));
}
return 0;
}

View File

@@ -0,0 +1,16 @@
listenai_library_named(app-mcp-tools)
listenai_library_sources(
mcp_tool_brightness.c
mcp_tool_volume.c
mcp_tool_device_info.c
mcp_tool_alarm.c
mcp_tool_led.c
mcp_tool_camera.c
mcp_tool_image.c
mcp_tool_duplex.c
mcp_tool_call_device.c
)
listenai_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@@ -0,0 +1,941 @@
#define TAG "alarm_control"
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <stdbool.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "lisa_mem.h"
#include "listen_system.h"
#include "mcp.h"
#include "alarm_next.h"
#include "service_alarm.h"
#define ALARM_CTRL_FAIL(_msg) \
do { \
snprintf(result_text, text_len, "%s", (_msg)); \
return -1; \
} while (0)
typedef struct {
uint64_t cloud_id; // cloud alarm id
const char *action; // CREATE / DELETE / UPDATE / QUERY
const char *alarm_type; // ONCE / DAILY / WEEKLY / WORKDAY / WEEKEND / MONTHLY / YEARLY
const char *holiday_name; // 节日名称
const char *calendar_type; // GREGORIAN / LUNAR
const char *time_str; // HH:mm:ss
const char *date_str; // YYYY-MM-DD
const char *lunar_date_str; // MM-DD (LUNAR)
int day_of_week; // 1-7
int day_of_month; // 1-31
int month_of_year; // 1-12
const char *text; // 提醒文本
cJSON *search_keywords; // array of strings
} alarm_control_params_t;
/* ==================== 参数解析工具 ==================== */
static void alarm_control_params_init(alarm_control_params_t *params)
{
if (!params) {
return;
}
memset(params, 0, sizeof(*params));
params->cloud_id = 0;
params->day_of_week = -1;
params->day_of_month = -1;
params->month_of_year = -1;
}
static void alarm_control_params_parse(cJSON *args, alarm_control_params_t *params)
{
if (!args || !params) {
return;
}
cJSON *pval = NULL;
pval = mcp_tool_call_args_get(args, "id");
if (pval && cJSON_IsNumber(pval)) {
params->cloud_id = (uint64_t)pval->valuedouble;
}
pval = mcp_tool_call_args_get(args, "action");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->action = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "alarm_type");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->alarm_type = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "holiday_name");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->holiday_name = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "calendar_type");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->calendar_type = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "time");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->time_str = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "date");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->date_str = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "lunar_date");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->lunar_date_str = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "day_of_week");
if (pval && cJSON_IsNumber(pval)) {
params->day_of_week = pval->valueint;
}
pval = mcp_tool_call_args_get(args, "day_of_month");
if (pval && cJSON_IsNumber(pval)) {
params->day_of_month = pval->valueint;
}
pval = mcp_tool_call_args_get(args, "month_of_year");
if (pval && cJSON_IsNumber(pval)) {
params->month_of_year = pval->valueint;
}
pval = mcp_tool_call_args_get(args, "text");
if (pval && cJSON_IsString(pval)) {
params->text = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "search_keywords");
if (pval && cJSON_IsArray(pval)) {
params->search_keywords = pval;
}
}
static alarm_calendar_t parse_calendar_type(const char *calendar_type)
{
if (calendar_type && strcmp(calendar_type, "LUNAR") == 0) {
return ALARM_CAL_LUNAR;
}
return ALARM_CAL_GREGORIAN;
}
static alarm_trigger_type_t parse_trigger_type(const char *alarm_type, const char *holiday_name)
{
if (holiday_name && holiday_name[0] != '\0') {
return ALARM_TRIG_HOLIDAY;
}
if (!alarm_type) {
return ALARM_TRIG_ONCE;
}
if (strcmp(alarm_type, "ONCE") == 0) {
return ALARM_TRIG_ONCE;
}
if (strcmp(alarm_type, "DAILY") == 0) {
return ALARM_TRIG_DAILY;
}
if (strcmp(alarm_type, "WEEKLY") == 0) {
return ALARM_TRIG_WEEKLY;
}
if (strcmp(alarm_type, "WORKDAY") == 0) {
return ALARM_TRIG_WORKDAY;
}
if (strcmp(alarm_type, "WEEKEND") == 0) {
return ALARM_TRIG_WEEKEND;
}
if (strcmp(alarm_type, "MONTHLY") == 0) {
return ALARM_TRIG_MONTHLY;
}
if (strcmp(alarm_type, "YEARLY") == 0) {
return ALARM_TRIG_YEARLY;
}
return ALARM_TRIG_ONCE;
}
static bool parse_time_hms(const char *time_str, uint8_t *hour, uint8_t *minute, uint8_t *second)
{
int h = -1, m = -1, s = -1;
if (!time_str || !hour || !minute || !second) {
return false;
}
if (sscanf(time_str, "%d:%d:%d", &h, &m, &s) == 3) {
// ok
} else if (sscanf(time_str, "%d:%d", &h, &m) == 2) {
s = 0;
} else {
return false;
}
if (h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59) {
return false;
}
*hour = (uint8_t)h;
*minute = (uint8_t)m;
*second = (uint8_t)s;
return true;
}
static bool parse_date_ymd(const char *date_str, uint16_t *year, uint8_t *month, uint8_t *day)
{
int y = -1, m = -1, d = -1;
if (!date_str || !year || !month || !day) {
return false;
}
if (sscanf(date_str, "%d-%d-%d", &y, &m, &d) != 3) {
return false;
}
if (y < 1970 || m < 1 || m > 12 || d < 1 || d > 31) {
return false;
}
*year = (uint16_t)y;
*month = (uint8_t)m;
*day = (uint8_t)d;
return true;
}
static bool parse_lunar_md(const char *lunar_date_str, uint8_t *month, uint8_t *day)
{
int m = -1, d = -1;
if (!lunar_date_str || !month || !day) {
return false;
}
if (sscanf(lunar_date_str, "%d-%d", &m, &d) != 2) {
return false;
}
if (m < 1 || m > 12 || d < 1 || d > 31) {
return false;
}
*month = (uint8_t)m;
*day = (uint8_t)d;
return true;
}
static void alarm_fill_keywords(const cJSON *arr, alarm_object_t *alarm)
{
if (!arr || !alarm) {
return;
}
uint8_t count = 0;
cJSON *item = NULL;
cJSON_ArrayForEach(item, arr) {
if (!cJSON_IsString(item) || !item->valuestring) {
continue;
}
if (count >= ALARM_KEYWORD_MAX) {
break;
}
strncpy(alarm->keywords[count], item->valuestring, ALARM_KEYWORD_LEN - 1);
alarm->keywords[count][ALARM_KEYWORD_LEN - 1] = '\0';
count++;
}
alarm->keyword_count = count;
}
/* ==================== 闹钟日期处理工具 ==================== */
static void normalize_tm(struct tm *tmv);
static int get_current_tm(struct tm *out_tm)
{
if (!out_tm) {
return -1;
}
struct timeval tv;
if (ls_sys_get_time(&tv) != 0) {
if (gettimeofday(&tv, NULL) != 0) {
return -1;
}
}
long int ts = (long int)tv.tv_sec;
if (!ls_sys_get_tmtime(&ts, out_tm)) {
return -1;
}
// ls_sys_get_tmtime uses year base 1970 and does not set tm_wday.
// Normalize via mktime with a 1900-based year offset, then convert back.
normalize_tm(out_tm);
return 0;
}
static void normalize_tm(struct tm *tmv)
{
if (!tmv) {
return;
}
struct tm tmp = *tmv;
tmp.tm_year += 70; // 1970-based -> 1900-based
mktime(&tmp);
tmv->tm_year = tmp.tm_year - 70;
tmv->tm_mon = tmp.tm_mon;
tmv->tm_mday = tmp.tm_mday;
tmv->tm_hour = tmp.tm_hour;
tmv->tm_min = tmp.tm_min;
tmv->tm_sec = tmp.tm_sec;
tmv->tm_wday = tmp.tm_wday;
tmv->tm_yday = tmp.tm_yday;
tmv->tm_isdst = tmp.tm_isdst;
}
static int days_in_month(int year, int month)
{
static const uint8_t days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month < 1 || month > 12) {
return 30;
}
if (month == 2) {
bool leap = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
return leap ? 29 : 28;
}
return days[month - 1];
}
static void set_date_from_tm(alarm_object_t *alarm, const struct tm *tmv)
{
if (!alarm || !tmv) {
return;
}
alarm->trigger.year = (uint16_t)(tmv->tm_year + 1970);
alarm->trigger.month = (uint8_t)(tmv->tm_mon + 1);
alarm->trigger.day = (uint8_t)tmv->tm_mday;
}
static int calc_tm_seconds(const struct tm *tmv)
{
if (!tmv) {
return 0;
}
return tmv->tm_hour * 3600 + tmv->tm_min * 60 + tmv->tm_sec;
}
static int calc_alarm_seconds(const alarm_object_t *alarm)
{
if (!alarm) {
return 0;
}
return alarm->trigger.hour * 3600 +
alarm->trigger.minute * 60 +
alarm->trigger.second;
}
static int set_nearest_date_from_workday_rule(alarm_object_t *alarm, struct tm *tmv, bool want_workday)
{
int now_sec = calc_tm_seconds(tmv);
int alarm_sec = calc_alarm_seconds(alarm);
LISA_LOGI(TAG, "workday calc start: want=%s now=%04d-%02d-%02d %02d:%02d:%02d alarm=%02u:%02u:%02u",
want_workday ? "workday" : "weekend",
tmv->tm_year + 1970, tmv->tm_mon + 1, tmv->tm_mday,
tmv->tm_hour, tmv->tm_min, tmv->tm_sec,
alarm->trigger.hour, alarm->trigger.minute, alarm->trigger.second);
for (int i = 0; i < 370; i++) {
// 查询日期是否工作日
char date_str[16] = {0};
snprintf(date_str, sizeof(date_str), "%02d-%02d", tmv->tm_mon + 1, tmv->tm_mday);
bool is_work_day = false;
if (listenai_date_is_workday(date_str, &is_work_day) != 0) {
LISA_LOGE(TAG, "workday query failed, date=%s", date_str);
return -6;
}
bool match = want_workday ? is_work_day : !is_work_day;
LISA_LOGI(TAG, "workday calc: i=%d date=%04d-%02d-%02d is_workday=%d match=%d",
i,
tmv->tm_year + 1970, tmv->tm_mon + 1, tmv->tm_mday,
is_work_day, match);
if (match) {
if (i > 0 || alarm_sec > now_sec) {
set_date_from_tm(alarm, tmv);
return 0;
}
}
tmv->tm_mday += 1;
normalize_tm(tmv);
}
LISA_LOGW(TAG, "workday rule not found within limit");
return -6;
}
// 获取未来最近的 周/月/年循环闹钟的日期
static int set_nearest_date_from_rule(alarm_object_t *alarm,
alarm_trigger_type_t trigger_type,
int day_of_week,
int day_of_month,
int month_of_year)
{
struct tm tmv;
if (get_current_tm(&tmv) != 0) {
return -1;
}
// Ensure tm_wday is normalized
normalize_tm(&tmv);
int now_sec = calc_tm_seconds(&tmv);
int alarm_sec = calc_alarm_seconds(alarm);
LISA_LOGI(TAG, "set_nearest_date_from_rule: now=%02d:%02d:%02d alarm=%02u:%02u:%02u",
tmv.tm_hour, tmv.tm_min, tmv.tm_sec,
alarm->trigger.hour, alarm->trigger.minute, alarm->trigger.second);
switch (trigger_type) {
case ALARM_TRIG_DAILY:
{
if (alarm_sec <= now_sec) {
tmv.tm_mday += 1;
normalize_tm(&tmv);
}
set_date_from_tm(alarm, &tmv);
return 0;
}
case ALARM_TRIG_WEEKLY:
{
if (day_of_week < 1 || day_of_week > 7) {
return -2;
}
int today_wd = tmv.tm_wday == 0 ? 7 : tmv.tm_wday; // 1=Mon ... 7=Sun
int diff = (day_of_week - today_wd + 7) % 7;
if (diff == 0) {
if (alarm_sec <= now_sec) {
diff = 7;
}
}
LISA_LOGI(TAG, "weekly calc: today_wd=%d target=%d diff=%d ",
today_wd, day_of_week, diff);
tmv.tm_mday += diff;
normalize_tm(&tmv);
set_date_from_tm(alarm, &tmv);
return 0;
}
case ALARM_TRIG_WORKDAY:
{
return set_nearest_date_from_workday_rule(alarm, &tmv, true);
}
case ALARM_TRIG_WEEKEND:
{
int today_wd = tmv.tm_wday == 0 ? 7 : tmv.tm_wday; // 1=Mon ... 7=Sun
bool today_is_weekend = (today_wd == 6 || today_wd == 7);
if (today_is_weekend && alarm_sec > now_sec) {
set_date_from_tm(alarm, &tmv);
return 0;
}
tmv.tm_mday += 1;
normalize_tm(&tmv);
for (int i = 0; i < 7; i++) {
int wd = tmv.tm_wday == 0 ? 7 : tmv.tm_wday;
if (wd == 6 || wd == 7) {
set_date_from_tm(alarm, &tmv);
return 0;
}
tmv.tm_mday += 1;
normalize_tm(&tmv);
}
LISA_LOGW(TAG, "weekend rule not found within limit");
return -6;
}
case ALARM_TRIG_MONTHLY:
{
if (day_of_month < 1 || day_of_month > 31) {
return -3;
}
LISA_LOGI(TAG, "monthly calc: today=%04d-%02d-%02d now=%02d:%02d:%02d target_day=%d ",
tmv.tm_year + 1970, tmv.tm_mon + 1, tmv.tm_mday,
tmv.tm_hour, tmv.tm_min, tmv.tm_sec);
int max_day = days_in_month(tmv.tm_year + 1970, tmv.tm_mon + 1);
int target_day = day_of_month > max_day ? max_day : day_of_month;
if (tmv.tm_mday < target_day) {
tmv.tm_mday = target_day;
} else if (tmv.tm_mday == target_day) {
if (alarm_sec <= now_sec) {
tmv.tm_mon += 1;
}
max_day = days_in_month(tmv.tm_year + 1970, tmv.tm_mon + 1);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
} else {
tmv.tm_mon += 1;
max_day = days_in_month(tmv.tm_year + 1970, tmv.tm_mon + 1);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
}
normalize_tm(&tmv);
set_date_from_tm(alarm, &tmv);
return 0;
}
case ALARM_TRIG_YEARLY:
{
if (month_of_year < 1 || month_of_year > 12 || day_of_month < 1 || day_of_month > 31) {
return -4;
}
LISA_LOGI(TAG, "yearly calc: today=%04d-%02d-%02d now=%02d:%02d:%02d target=%02d-%02d ",
tmv.tm_year + 1970, tmv.tm_mon + 1, tmv.tm_mday,
tmv.tm_hour, tmv.tm_min, tmv.tm_sec,
month_of_year, day_of_month);
int max_day = days_in_month(tmv.tm_year + 1970, month_of_year);
int target_day = day_of_month > max_day ? max_day : day_of_month;
if ((tmv.tm_mon + 1 < month_of_year) ||
((tmv.tm_mon + 1 == month_of_year) && (tmv.tm_mday < target_day))) {
tmv.tm_mon = month_of_year - 1;
tmv.tm_mday = target_day;
} else if ((tmv.tm_mon + 1 == month_of_year) && (tmv.tm_mday == target_day)) {
if (alarm_sec <= now_sec) {
tmv.tm_year += 1;
}
tmv.tm_mon = month_of_year - 1;
max_day = days_in_month(tmv.tm_year + 1970, month_of_year);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
} else {
tmv.tm_year += 1;
tmv.tm_mon = month_of_year - 1;
max_day = days_in_month(tmv.tm_year + 1970, month_of_year);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
}
normalize_tm(&tmv);
set_date_from_tm(alarm, &tmv);
return 0;
}
default:
return -5;
}
}
/* ==================== 闹钟对象构造工具 ==================== */
static int build_alarm_by_params(const alarm_control_params_t *params, alarm_object_t *alarm_obj,
char *err_msg, size_t err_len)
{
if (!params || !alarm_obj || !err_msg || err_len == 0) {
return -1;
}
memset(alarm_obj, 0, sizeof(*alarm_obj));
alarm_obj->alarm_id = 0;
alarm_obj->cloud_id = params->cloud_id;
alarm_obj->calendar = parse_calendar_type(params->calendar_type);
alarm_obj->trigger.type = parse_trigger_type(params->alarm_type, params->holiday_name);
// 检查闹钟时间(必选)
if (!params->time_str || params->time_str[0] == '\0') {
LISA_LOGE(TAG, "time missing");
snprintf(err_msg, err_len, "没有指定时间或时间格式错误");
return -1;
}
if (!parse_time_hms(params->time_str, &alarm_obj->trigger.hour, &alarm_obj->trigger.minute, &alarm_obj->trigger.second)) {
LISA_LOGE(TAG, "invalid time format: %s", params->time_str);
snprintf(err_msg, err_len, "没有指定时间或时间格式错误");
return -1;
}
// 检查闹钟日期
bool date_set = false;
if (params->date_str) {
uint16_t y = 0;
uint8_t m = 0;
uint8_t d = 0;
if (parse_date_ymd(params->date_str, &y, &m, &d)) {
alarm_obj->trigger.year = y;
alarm_obj->trigger.month = m;
alarm_obj->trigger.day = d;
date_set = true;
}
}
// 如果没有下发日期,构造闹钟日期
// 先按日历类型构造
if (!date_set) {
if (alarm_obj->calendar == ALARM_CAL_LUNAR) {
if (!params->lunar_date_str || params->lunar_date_str[0] == '\0') {
LISA_LOGE(TAG, "lunar_date missing");
snprintf(err_msg, err_len, "缺少农历日期");
return -1;
}
char gregorian_date[16] = {0};
if (listenai_date_transition("lunar", params->lunar_date_str,
gregorian_date, sizeof(gregorian_date)) != 0) {
LISA_LOGE(TAG, "lunar transition failed");
snprintf(err_msg, err_len, "农历日期转换失败");
return -1;
}
uint16_t y = 0;
uint8_t m = 0;
uint8_t d = 0;
if (!parse_date_ymd(gregorian_date, &y, &m, &d)) {
LISA_LOGE(TAG, "invalid lunar transition date: %s", gregorian_date);
snprintf(err_msg, err_len, "农历日期转换失败");
return -1;
}
alarm_obj->trigger.year = y;
alarm_obj->trigger.month = m;
alarm_obj->trigger.day = d;
date_set = true;
}
}
// 再按闹钟类型构造
if (!date_set) {
if (alarm_obj->trigger.type == ALARM_TRIG_ONCE) {
struct tm tmv;
if (get_current_tm(&tmv) != 0) {
LISA_LOGE(TAG, "get today date failed");
snprintf(err_msg, err_len, "无法获取当前日期");
return -1;
}
set_date_from_tm(alarm_obj, &tmv);
date_set = true;
} else if (alarm_obj->trigger.type == ALARM_TRIG_DAILY ||
alarm_obj->trigger.type == ALARM_TRIG_WEEKLY ||
alarm_obj->trigger.type == ALARM_TRIG_MONTHLY ||
alarm_obj->trigger.type == ALARM_TRIG_YEARLY ||
alarm_obj->trigger.type == ALARM_TRIG_WORKDAY||
alarm_obj->trigger.type == ALARM_TRIG_WEEKEND ) {
int r = set_nearest_date_from_rule(alarm_obj, alarm_obj->trigger.type,
params->day_of_week, params->day_of_month, params->month_of_year);
if (r != 0) {
LISA_LOGE(TAG, "missing or invalid rule params, type=%d", alarm_obj->trigger.type);
snprintf(err_msg, err_len, "缺少必要的日期参数");
return -1;
}
}else if (alarm_obj->trigger.type == ALARM_TRIG_HOLIDAY) {
if (!params->holiday_name || params->holiday_name[0] == '\0') {
LISA_LOGE(TAG, "holiday_name missing");
snprintf(err_msg, err_len, "缺少节日名称");
return -1;
}
char gregorian_date[16] = {0};
if (listenai_date_transition("holiday", params->holiday_name,
gregorian_date, sizeof(gregorian_date)) != 0) {
LISA_LOGE(TAG, "holiday transition failed");
snprintf(err_msg, err_len, "节假日转换失败");
return -1;
}
uint16_t y = 0;
uint8_t m = 0;
uint8_t d = 0;
if (!parse_date_ymd(gregorian_date, &y, &m, &d)) {
LISA_LOGE(TAG, "invalid holiday transition date: %s", gregorian_date);
snprintf(err_msg, err_len, "节假日转换失败");
return -1;
}
alarm_obj->trigger.year = y;
alarm_obj->trigger.month = m;
alarm_obj->trigger.day = d;
date_set = true;
}
}
// 保存时间戳
alarm_obj->alarm_id = alarm_obj_to_timestamp(alarm_obj);
if (alarm_obj->alarm_id == 0) {
LISA_LOGE(TAG, "invalid alarm datetime, cannot build alarm_id");
snprintf(err_msg, err_len, "闹钟时间无效");
return -1;
}
// 保存循环信息
if (params->day_of_week >= 1 && params->day_of_week <= 7) {
alarm_obj->trigger.day_of_week = (uint8_t)params->day_of_week;
}
if (params->day_of_month >= 1 && params->day_of_month <= 31) {
alarm_obj->trigger.day_of_month = (uint8_t)params->day_of_month;
}
if (params->month_of_year >= 1 && params->month_of_year <= 12) {
alarm_obj->trigger.month = (uint8_t)params->month_of_year;
}
if (params->holiday_name && params->holiday_name[0] != '\0') {
strncpy(alarm_obj->trigger.holiday_name, params->holiday_name, sizeof(alarm_obj->trigger.holiday_name) - 1);
alarm_obj->trigger.holiday_name[sizeof(alarm_obj->trigger.holiday_name) - 1] = '\0';
}
if (params->lunar_date_str && alarm_obj->calendar == ALARM_CAL_LUNAR) {
uint8_t m = 0;
uint8_t d = 0;
if (parse_lunar_md(params->lunar_date_str, &m, &d)) {
alarm_obj->trigger.lunar_month = m;
alarm_obj->trigger.lunar_day = d;
} else {
LISA_LOGW(TAG, "invalid lunar_date format: %s", params->lunar_date_str);
}
}
// 保存文本和关键词
if (params->text && params->text[0] != '\0') {
strncpy(alarm_obj->text, params->text, sizeof(alarm_obj->text) - 1);
alarm_obj->text[sizeof(alarm_obj->text) - 1] = '\0';
}
if (params->search_keywords) {
alarm_fill_keywords(params->search_keywords, alarm_obj);
}
return 0;
}
/* ==================== 闹钟对象查找工具 ==================== */
static int delete_alarm_by_cloud_id(uint64_t cloud_id, char *result_text, size_t text_len)
{
return service_alarm_delete_by_cloud_id(cloud_id, result_text, text_len);
}
/**
* @brief 闹钟提醒相关功能的处理函数
*
* 该工具用于处理闹钟提醒的创建、查询、删除操作。
* 首先构造 alarm_obj 闹钟对象,
* 然后基于 action 调用不同的函数处理闹钟操作指令,
* 最后基于处理结果构造 MCP 响应。
*/
static int alarm_control_execute(const alarm_control_params_t *params, char *result_text, size_t text_len)
{
if (!params || !result_text || text_len == 0) {
ALARM_CTRL_FAIL("缺少参数");
}
if (!params->action || params->action[0] == '\0') {
ALARM_CTRL_FAIL("缺少操作类型");
}
if (params->cloud_id == 0) {
ALARM_CTRL_FAIL("缺少闹钟id");
}
result_text[0] = '\0';
// 执行闹钟指令
if (params->action && strcmp(params->action, "CREATE") == 0) {
alarm_object_t alarm_obj;
memset(&alarm_obj, 0, sizeof(alarm_obj));
if (build_alarm_by_params(params, &alarm_obj, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
alarm_obj_print(&alarm_obj);
if (service_alarm_create_obj(&alarm_obj, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
} else if (params->action && strcmp(params->action, "DELETE") == 0) {
if (service_alarm_delete_by_cloud_id(params->cloud_id, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
} else if (params->action && strcmp(params->action, "UPDATE") == 0) {
if (service_alarm_delete_by_cloud_id(params->cloud_id, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
alarm_object_t new_alarm;
memset(&new_alarm, 0, sizeof(new_alarm));
if (build_alarm_by_params(params, &new_alarm, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
alarm_obj_print(&new_alarm);
if (service_alarm_create_obj(&new_alarm, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
} else if (params->action && strcmp(params->action, "QUERY") == 0) {
const alarm_object_t *target = service_alarm_find_by_cloud_id(params->cloud_id);
if (!target) {
ALARM_CTRL_FAIL("未找到对应闹钟");
}
snprintf(result_text, text_len, "查询成功, id=%llu, timestamp=%llu, text=%s",
(unsigned long long)target->cloud_id,
(unsigned long long)target->alarm_id,
target->text);
} else {
ALARM_CTRL_FAIL("不支持的操作类型");
}
return 0;
}
static cJSON *alarm_control_call(const char *id, const char *name, cJSON *args)
{
(void)id;
alarm_control_params_t params;
alarm_control_params_init(&params);
alarm_control_params_parse(args, &params);
char result_text[512] = {0};
int exec_ret = alarm_control_execute(&params, result_text, sizeof(result_text));
if (result_text[0] == '\0') {
snprintf(result_text, sizeof(result_text), "%s", (exec_ret == 0) ? "闹钟操作成功" : "闹钟操作失败");
}
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON_AddStringToObject(result, "content", result_text);
cJSON_AddBoolToObject(result, "isError", exec_ret != 0);
return result;
}
static cJSON *alarm_control_list(const char *name)
{
cJSON *tool = cJSON_CreateObject();
if (!tool) {
return NULL;
}
cJSON_AddStringToObject(tool, "name", name);
cJSON_AddStringToObject(tool, "description",
"智能闹钟与提醒管理系统。支持公历/农历、固定周期及动态节假日。核心准则1.【禁止猜测时间】若用户仅提供下午早晨待会以后等模糊时段或未提及具体时间严禁私自编造时间点此时必须放弃调用函数转而向用户询问具体几点几分。2. 识别到特定节日必须填充 holiday_name3. 只要存在提醒内容(text),就必须同步提取搜索关键词(search_keywords)以供后续索引。");
cJSON *schema = cJSON_CreateObject();
if (!schema) {
cJSON_Delete(tool);
return NULL;
}
cJSON_AddStringToObject(schema, "type", "object");
cJSON *properties = cJSON_CreateObject();
if (!properties) {
cJSON_Delete(schema);
cJSON_Delete(tool);
return NULL;
}
cJSON *action_prop = cJSON_CreateObject();
cJSON_AddStringToObject(action_prop, "type", "string");
cJSON *action_enum = cJSON_CreateArray();
cJSON_AddItemToArray(action_enum, cJSON_CreateString("CREATE"));
cJSON_AddItemToArray(action_enum, cJSON_CreateString("DELETE"));
cJSON_AddItemToArray(action_enum, cJSON_CreateString("UPDATE"));
cJSON_AddItemToArray(action_enum, cJSON_CreateString("QUERY"));
cJSON_AddItemToObject(action_prop, "enum", action_enum);
cJSON_AddStringToObject(action_prop, "description", "操作类型CREATE(新建), DELETE(删除), UPDATE(修改), QUERY(查询)。");
cJSON_AddItemToObject(properties, "action", action_prop);
cJSON *alarm_type_prop = cJSON_CreateObject();
cJSON_AddStringToObject(alarm_type_prop, "type", "string");
cJSON *alarm_type_enum = cJSON_CreateArray();
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("ONCE"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("DAILY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("WEEKLY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("WORKDAY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("WEEKEND"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("MONTHLY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("YEARLY"));
cJSON_AddItemToObject(alarm_type_prop, "enum", alarm_type_enum);
cJSON_AddStringToObject(alarm_type_prop, "description", "循环策略。节日提醒固定使用 YEARLY。");
cJSON_AddItemToObject(properties, "alarm_type", alarm_type_prop);
cJSON *holiday_name_prop = cJSON_CreateObject();
cJSON_AddStringToObject(holiday_name_prop, "type", "string");
cJSON_AddStringToObject(holiday_name_prop, "description", "节日名称(如'中秋节'、'母亲节')。若用户提及节日,此项必填。");
cJSON_AddItemToObject(properties, "holiday_name", holiday_name_prop);
cJSON *calendar_type_prop = cJSON_CreateObject();
cJSON_AddStringToObject(calendar_type_prop, "type", "string");
cJSON *calendar_type_enum = cJSON_CreateArray();
cJSON_AddItemToArray(calendar_type_enum, cJSON_CreateString("GREGORIAN"));
cJSON_AddItemToArray(calendar_type_enum, cJSON_CreateString("LUNAR"));
cJSON_AddItemToObject(calendar_type_prop, "enum", calendar_type_enum);
cJSON_AddStringToObject(calendar_type_prop, "default", "GREGORIAN");
cJSON_AddStringToObject(calendar_type_prop, "description", "历法GREGORIAN(公历), LUNAR(农历)。");
cJSON_AddItemToObject(properties, "calendar_type", calendar_type_prop);
cJSON *time_prop = cJSON_CreateObject();
cJSON_AddStringToObject(time_prop, "type", "string");
cJSON_AddStringToObject(time_prop, "description", "24小时制时间 (HH:mm:ss)。严禁推测,未提及则反问用户。");
cJSON_AddItemToObject(properties, "time", time_prop);
cJSON *date_prop = cJSON_CreateObject();
cJSON_AddStringToObject(date_prop, "type", "string");
cJSON_AddStringToObject(date_prop, "description", "具体日期 (YYYY-MM-DD)。仅在 ONCE 模式下使用。");
cJSON_AddItemToObject(properties, "date", date_prop);
cJSON *lunar_date_prop = cJSON_CreateObject();
cJSON_AddStringToObject(lunar_date_prop, "type", "string");
cJSON_AddStringToObject(lunar_date_prop, "description",
"具体日期 (MM-DD)。仅在 calendar_type 为 LUNAR 模式下使用。需要输出农历对应的数字月日格式正月初三输出01-03");
cJSON_AddItemToObject(properties, "lunar_date", lunar_date_prop);
cJSON *day_of_week_prop = cJSON_CreateObject();
cJSON_AddStringToObject(day_of_week_prop, "type", "integer");
cJSON_AddNumberToObject(day_of_week_prop, "minimum", 1);
cJSON_AddNumberToObject(day_of_week_prop, "maximum", 7);
cJSON_AddStringToObject(day_of_week_prop, "description", "周几1-71代表周一。仅在 WEEKLY 模式有效。");
cJSON_AddItemToObject(properties, "day_of_week", day_of_week_prop);
cJSON *day_of_month_prop = cJSON_CreateObject();
cJSON_AddStringToObject(day_of_month_prop, "type", "integer");
cJSON_AddNumberToObject(day_of_month_prop, "minimum", 1);
cJSON_AddNumberToObject(day_of_month_prop, "maximum", 31);
cJSON_AddStringToObject(day_of_month_prop, "description", "每月几号。仅在 MONTHLY 或无节日的 YEARLY 模式有效。");
cJSON_AddItemToObject(properties, "day_of_month", day_of_month_prop);
cJSON *month_of_year_prop = cJSON_CreateObject();
cJSON_AddStringToObject(month_of_year_prop, "type", "integer");
cJSON_AddNumberToObject(month_of_year_prop, "minimum", 1);
cJSON_AddNumberToObject(month_of_year_prop, "maximum", 12);
cJSON_AddStringToObject(month_of_year_prop, "description", "每年几月。仅在无节日的 YEARLY 模式有效。");
cJSON_AddItemToObject(properties, "month_of_year", month_of_year_prop);
cJSON *text_prop = cJSON_CreateObject();
cJSON_AddStringToObject(text_prop, "type", "string");
cJSON_AddStringToObject(text_prop, "description", "提醒的完整文本内容。例如:'给妈妈打电话庆祝生日'。");
cJSON_AddItemToObject(properties, "text", text_prop);
cJSON *search_keywords_prop = cJSON_CreateObject();
cJSON_AddStringToObject(search_keywords_prop, "type", "array");
cJSON *keywords_items = cJSON_CreateObject();
cJSON_AddStringToObject(keywords_items, "type", "string");
cJSON_AddItemToObject(search_keywords_prop, "items", keywords_items);
cJSON_AddStringToObject(search_keywords_prop, "description", "【强制要求】从 text 中提取的 1-3 个核心动词或名词。若 text 存在,此项禁止为空。例如 text 为'记得下午三点喝水',关键词为 ['喝水']。");
cJSON_AddItemToObject(properties, "search_keywords", search_keywords_prop);
cJSON_AddItemToObject(schema, "properties", properties);
cJSON *required = cJSON_CreateArray();
cJSON_AddItemToArray(required, cJSON_CreateString("action"));
cJSON_AddItemToObject(schema, "required", required);
cJSON_AddItemToObject(tool, "inputSchema", schema);
return tool;
}
MCP_TOOL_DEFINE(ls.built_in.alarm_clock, alarm_control_list, alarm_control_call);

View File

@@ -0,0 +1,161 @@
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define TAG "mcp.tool.brightness"
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "service_brightness.h"
static int brightness_clamp(int value)
{
if (value < 0) {
return 0;
}
if (value > 100) {
return 100;
}
return value;
}
static cJSON *brightness_result(const char *name, const char *text, bool is_error)
{
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
if (!content_array || !content_item) {
if (content_array) {
cJSON_Delete(content_array);
}
cJSON_Delete(content_item);
cJSON_Delete(result);
return NULL;
}
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", text);
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", is_error);
return result;
}
static cJSON *brightness_control_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(
name,
"显示器亮度控制与调节工具。当用户发出调节亮度、控制屏幕亮度的指令时调用。支持绝对值设置调到50%%亮度、相对值调节调亮一点、调暗1档以及极值控制最亮、最暗");
if (!tool) {
return NULL;
}
mcp_tool_info_add_property(tool, "text", "用户输入的关于亮度调节的意图", "string", true);
cJSON *intent_property = cJSON_CreateObject();
cJSON_AddStringToObject(intent_property, "type", "string");
cJSON_AddStringToObject(intent_property, "description",
"调节类型set设定到具体值或极值调到50%%亮度、最亮、最暗adjustUp调亮、加一档adjustDown调暗、暗一点");
cJSON *intent_enum = cJSON_CreateArray();
cJSON_AddItemToArray(intent_enum, cJSON_CreateString("set"));
cJSON_AddItemToArray(intent_enum, cJSON_CreateString("adjustUp"));
cJSON_AddItemToArray(intent_enum, cJSON_CreateString("adjustDown"));
cJSON_AddItemToObject(intent_property, "enum", intent_enum);
mcp_tool_info_add_json_property(tool, "intent", intent_property, true);
mcp_tool_info_add_property(
tool,
"value",
"具体的数值或状态1.数字为 10、50 等时,代表绝对亮度百分比;数字为 1、2 等时,代表相对调整档位(每档 10%%2. 标准状态位max 对应最亮min 对应最暗;无明确参数时留空(如 “调亮一点儿”)。",
"string",
false);
cJSON *unit_property = cJSON_CreateObject();
cJSON_AddStringToObject(unit_property, "type", "string");
cJSON_AddStringToObject(unit_property, "description", "描述数值的单位。若指令中提及'档'、'级'、'%%'则对应填写,否则留空。");
cJSON *unit_enum = cJSON_CreateArray();
cJSON_AddItemToArray(unit_enum, cJSON_CreateString(""));
cJSON_AddItemToArray(unit_enum, cJSON_CreateString(""));
cJSON_AddItemToArray(unit_enum, cJSON_CreateString("百分比"));
cJSON_AddItemToArray(unit_enum, cJSON_CreateString(""));
cJSON_AddItemToObject(unit_property, "enum", unit_enum);
mcp_tool_info_add_json_property(tool, "unit", unit_property, false);
return tool;
}
static cJSON *brightness_control_call(const char *id, const char *name, cJSON *args)
{
(void)id;
const cJSON *text_json = mcp_tool_call_args_get(args, "text");
const cJSON *intent_json = mcp_tool_call_args_get(args, "intent");
const cJSON *value_json = mcp_tool_call_args_get(args, "value");
const cJSON *unit_json = mcp_tool_call_args_get(args, "unit");
if (!text_json || !cJSON_IsString(text_json)) {
LOGE("text parameter not found or invalid");
return brightness_result(name, "text 参数缺失或格式错误。", true);
}
if (!intent_json || !cJSON_IsString(intent_json)) {
LOGE("intent parameter not found or invalid");
return brightness_result(name, "intent 参数缺失或格式错误。", true);
}
const char *text = text_json->valuestring;
const char *intent = intent_json->valuestring;
const char *value = value_json && cJSON_IsString(value_json) ? value_json->valuestring : NULL;
const char *unit = unit_json && cJSON_IsString(unit_json) ? unit_json->valuestring : "";
LOGI("Brightness control - text: %s, intent: %s, value: %s, unit: %s",
text, intent, value ? value : "null", unit);
if (strcmp(intent, "set") == 0) {
if (value) {
if (strcmp(value, "max") == 0) {
service_brightness_set(100);
} else if (strcmp(value, "min") == 0) {
service_brightness_set(0);
} else {
int target = brightness_clamp(atoi(value));
service_brightness_set(target);
}
}
} else if (strcmp(intent, "adjustUp") == 0 || strcmp(intent, "adjustDown") == 0) {
int step = 10;
if (value) {
int parsed = atoi(value);
if (parsed > 0) {
if (strcmp(unit, "百分比") == 0) {
step = parsed;
} else {
step = parsed * 10;
}
}
}
int current = service_brightness_get();
int target = current;
if (strcmp(intent, "adjustUp") == 0) {
target = brightness_clamp(current + step);
} else {
target = brightness_clamp(current - step);
}
service_brightness_set(target);
} else {
LOGE("Unsupported intent: %s", intent);
return brightness_result(name, "intent 不支持,必须是 set/adjustUp/adjustDown。", true);
}
return brightness_result(name, "已完成操作", false);
}
MCP_TOOL_DEFINE(ls.display_set_brightness, brightness_control_list, brightness_control_call);

View File

@@ -0,0 +1,49 @@
#include <string.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#define TAG "mcp_call_device"
int voice_livekit_bridge_request_call(const char *to_id);
static cJSON *call_device_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name, "发起设备通话");
if (!tool) {
return NULL;
}
mcp_tool_info_add_property(tool, "toId", "目标设备号", "string", true);
return tool;
}
static cJSON *call_device_call(const char *id, const char *name, cJSON *args)
{
(void)id;
const cJSON *to_id_json = mcp_tool_call_args_get(args, "toId");
const char *to_id = cJSON_GetStringValue(to_id_json);
int ret = -1;
if (!to_id || to_id[0] == '\0') {
LOGE("mcp call_device missing toId");
} else {
LOGI("mcp call_device received toId=%s", to_id);
ret = voice_livekit_bridge_request_call(to_id);
}
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON_AddStringToObject(result, "content", ret == 0 ? "呼叫已发起" : "呼叫发起失败");
cJSON_AddNumberToObject(result, "ret", ret);
return result;
}
MCP_TOOL_DEFINE(ls.built_in.call_device, call_device_list, call_device_call);

View File

@@ -0,0 +1,75 @@
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "service_camera.h"
#include "sysheap.h"
#include "voice_msg.h"
static cJSON *take_photo_result_text(const char *name, const char *text, bool is_error)
{
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
if (!content_array || !content_item) {
if (content_array) {
cJSON_Delete(content_array);
}
if (content_item) {
cJSON_Delete(content_item);
}
cJSON_Delete(result);
return NULL;
}
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", text);
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", is_error);
return result;
}
static cJSON *take_photo_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name, "拍照");
if (!tool) {
return NULL;
}
return tool;
}
static cJSON *take_photo_call(const char *id, const char *name, cJSON *args)
{
(void)args;
if (!id || id[0] == '\0') {
LOGE("take photo failed: invalid mcp id");
return take_photo_result_text(name, "拍照请求参数错误。", true);
}
if (!service_camera_is_inited()) {
int ret = service_camera_init();
if (ret != 0) {
LOGE("take photo failed: camera init error %d", ret);
return take_photo_result_text(name, "摄像头初始化失败,无法拍照。", true);
}
}
LOGI("Taking photo...");
voice_msg_pub(VOICE_MSG_CLOUD_MCP_IMAGE_RECOGNITION, (void *)id, strlen(id) + 1);
return NULL;
}
MCP_TOOL_DEFINE(ls.built_in.take_photo, take_photo_list, take_photo_call);

View File

@@ -0,0 +1,145 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "project_version.h"
#include "service_brightness.h"
#include "service_volume.h"
#define TAG "mcp_device_info"
static cJSON *device_info_result_text(const char *name, const char *text, bool is_error)
{
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
if (!content_array || !content_item) {
if (content_array) {
cJSON_Delete(content_array);
}
cJSON_Delete(content_item);
cJSON_Delete(result);
return NULL;
}
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", text);
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", is_error);
return result;
}
static cJSON *device_info_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(
name,
"查询设备的状态信息。可以同时获取当前音量、当前屏幕亮度、以及设备固件版本号。当用户询问这些信息时调用。");
if (!tool) {
return NULL;
}
cJSON *fields_property = cJSON_CreateObject();
cJSON *items = cJSON_CreateObject();
cJSON *enum_array = cJSON_CreateArray();
if (!fields_property || !items || !enum_array) {
cJSON_Delete(fields_property);
cJSON_Delete(items);
cJSON_Delete(enum_array);
cJSON_Delete(tool);
return NULL;
}
cJSON_AddStringToObject(fields_property, "type", "array");
cJSON_AddStringToObject(
fields_property,
"description",
"需要查询的信息字段列表。可包含 'volume'(音量), 'brightness'(亮度), 'version'(版本号)中的一个或多个。");
cJSON_AddStringToObject(items, "type", "string");
cJSON_AddItemToArray(enum_array, cJSON_CreateString("volume"));
cJSON_AddItemToArray(enum_array, cJSON_CreateString("brightness"));
cJSON_AddItemToArray(enum_array, cJSON_CreateString("version"));
cJSON_AddItemToObject(items, "enum", enum_array);
cJSON_AddItemToObject(fields_property, "items", items);
mcp_tool_info_add_json_property(tool, "fields", fields_property, true);
return tool;
}
static cJSON *device_info_call(const char *id, const char *name, cJSON *args)
{
(void)id;
const cJSON *fields_json = mcp_tool_call_args_get(args, "fields");
if (!fields_json || !cJSON_IsArray(fields_json)) {
return device_info_result_text(name, "参数 fields 缺失或格式错误,必须是数组。", true);
}
bool need_volume = false;
bool need_brightness = false;
bool need_version = false;
cJSON *field = NULL;
cJSON_ArrayForEach(field, fields_json)
{
if (!cJSON_IsString(field) || !field->valuestring) {
return device_info_result_text(name, "fields 中存在非字符串元素。", true);
}
if (strcmp(field->valuestring, "volume") == 0) {
need_volume = true;
} else if (strcmp(field->valuestring, "brightness") == 0) {
need_brightness = true;
} else if (strcmp(field->valuestring, "version") == 0) {
need_version = true;
} else {
char error_text[128];
snprintf(error_text, sizeof(error_text), "fields 包含不支持的字段: %s", field->valuestring);
return device_info_result_text(name, error_text, true);
}
}
if (!need_volume && !need_brightness && !need_version) {
return device_info_result_text(name, "fields 不能为空。", true);
}
cJSON *payload = cJSON_CreateObject();
if (!payload) {
return NULL;
}
if (need_volume) {
cJSON_AddNumberToObject(payload, "volume", service_volume_get());
}
if (need_brightness) {
cJSON_AddNumberToObject(payload, "brightness", service_brightness_get());
}
if (need_version) {
cJSON_AddStringToObject(payload, "version", PROJECT_VERSION_STR);
}
char *payload_text = cJSON_PrintUnformatted(payload);
cJSON_Delete(payload);
if (!payload_text) {
return NULL;
}
cJSON *result = device_info_result_text(name, payload_text, false);
cJSON_free(payload_text);
LOGI("get_device_info called, volume=%d brightness=%d version=%d",
need_volume, need_brightness, need_version);
return result;
}
MCP_TOOL_DEFINE(ls.get_device_info, device_info_list, device_info_call);

View File

@@ -0,0 +1,80 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "app_datas.h"
#include "kv_user.h"
#include "lisa_kv.h"
#include "voice_cloud.h"
#include "apps/llm/models/model_voice.h"
#include "voice_msg.h"
#define TAG "mcp_tool_duplex"
static cJSON *duplex_switch_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name,
"该工具用于切换交互模式,在[单工模式/半双工模式/单轮对话] 与 [全双工模式/连续对话/多轮对话] 这两种对话模式之间切换");
if (!tool) {
return NULL;
}
mcp_tool_info_add_property(tool, "value","开关true为进入[全双工模式/连续对话/多轮对话]false为[单工模式/半双工模式/单轮对话]","boolean", true);
return tool;
}
static cJSON *duplex_switch_call(const char *id, const char *name, cJSON *args)
{
const cJSON *value_json = mcp_tool_call_args_get(args, "value");
if (!value_json || !cJSON_IsBool(value_json)) {
LOGE("value parameter not found or invalid");
return NULL;
}
bool enable_full_duplex = cJSON_IsTrue(value_json);
LOGI("switch duplex -> %s", enable_full_duplex ? "full" : "half");
model_voice_wakeup_mode_t mode = enable_full_duplex ? MODEL_VOICE_WAKEUP_MODE_VOICE_MULTI : MODEL_VOICE_WAKEUP_MODE_VOICE_SINGLE;
if (model_voice_wakeup_mode_set(mode) != 0) {
LOGW("model_voice_wakeup_mode_set failed, fallback to app_datas only");
}
struct app_datas *app_datas = get_app_datas();
if (app_datas) {
app_datas->full_duplex = enable_full_duplex;
app_datas->voice_work_mode &= ~VOICE_WORK_MODE_BUTTON_WAKEUP;
app_datas->voice_work_mode |= VOICE_WORK_MODE_VOICE_WAKEUP;
lisa_kv_set_bool(KV_KEY_FULL_DUPLEX, app_datas->full_duplex);
lisa_kv_set_int(KV_KEY_WAKEUP_MODE, (int)app_datas->voice_work_mode);
}
voice_msg_pub(VOICE_MSG_CLOUD_MCP_CHAT_EXIT, NULL, 0);
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
char text[96];
const char *mode_desc = enable_full_duplex ? "全双工模式" : "单工模式";
snprintf(text, sizeof(text), "已切换到%s", mode_desc);
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", text);
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", false);
return result;
}
MCP_TOOL_DEFINE(ls.built_in.switch_full_duplex, duplex_switch_list, duplex_switch_call);

View File

@@ -0,0 +1,71 @@
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "voice_msg.h"
#include "image_url_utils.h"
#include "service_image.h"
#define TAG "mcp_tool_image"
static cJSON *show_image_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name, "显示网络图片参数为图片URL");
if (!tool) {
return NULL;
}
mcp_tool_info_add_property(tool, "url", "图片URL支持http/https", "string", true);
return tool;
}
static cJSON *show_image_call(const char *id, const char *name, cJSON *args)
{
(void)id;
const cJSON *url_json = mcp_tool_call_args_get(args, "url");
if (!url_json || !cJSON_IsString(url_json) || !url_json->valuestring || url_json->valuestring[0] == '\0') {
LOGE("url parameter not found or invalid");
return NULL;
}
const char *url = url_json->valuestring;
char display_url[768] = {0};
if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0) {
LOGE("url format invalid: %s", url);
return NULL;
}
if (build_image_display_url(url, display_url, sizeof(display_url)) != 0) {
LOGE("build display url failed: %s", url);
return NULL;
}
if (!service_image_waiting_get()) {
LOGW("drop image url because not in waiting state");
} else {
LOGI("show image url: %s", display_url);
voice_msg_pub(VOICE_MSG_CLOUD_MCP_LOADING, NULL, 0);
voice_msg_pub(VOICE_MSG_CLOUD_MCP_IMAGE_URL, (void *)display_url, strlen(display_url) + 1);
}
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", "已完成操作");
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", false);
return result;
}
MCP_TOOL_DEFINE(ls.built_in.show_image, show_image_list, show_image_call);

View File

@@ -0,0 +1,112 @@
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "service_led.h"
#define TAG "mcp_tool_led"
static cJSON *led_switch_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name, "控制LED开关状态可以是开启或关闭");
if (!tool) {
return NULL;
}
mcp_tool_info_add_property(tool, "value", "LED开关状态打开为true关闭为false", "boolean", true);
return tool;
}
static cJSON *led_switch_call(const char *id, const char *name, cJSON *args)
{
const cJSON *value_json = mcp_tool_call_args_get(args, "value");
if (!value_json || !cJSON_IsBool(value_json)) {
LOGE("value parameter not found or invalid");
return NULL;
}
bool value = cJSON_IsTrue(value_json);
LOGI("LED switch: %s", value ? "on" : "off");
if (value) {
service_led_on();
} else {
service_led_off();
}
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", "已完成操作");
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", false);
return result;
}
static cJSON *led_blink_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name, "控制LED闪烁模式可以是关闭、普通、快速或慢速");
if (!tool) {
return NULL;
}
mcp_tool_info_add_property(tool, "mode", "LED闪烁模式可以是 'off'、'normal'、'fast' 或 'slow'", "string", true);
return tool;
}
static cJSON *led_blink_call(const char *id, const char *name, cJSON *args)
{
const cJSON *mode_json = mcp_tool_call_args_get(args, "mode");
if (!mode_json || !cJSON_IsString(mode_json)) {
LOGE("mode parameter not found or invalid");
return NULL;
}
const char *mode = mode_json->valuestring;
LOGI("LED blink mode: %s", mode);
if (strcmp(mode, "off") == 0) {
service_led_off();
} else if (strcmp(mode, "fast") == 0) {
service_led_blink(200, 200);
} else if (strcmp(mode, "slow") == 0) {
service_led_blink(1000, 1000);
} else if (strcmp(mode, "normal") == 0) {
service_led_blink(500, 500);
} else {
LOGE("Invalid mode: %s", mode);
return NULL;
}
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", "已完成操作");
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", false);
return result;
}
MCP_TOOL_DEFINE(ls.led_switch, led_switch_list, led_switch_call);
MCP_TOOL_DEFINE(ls.led_blink, led_blink_list, led_blink_call);

View File

@@ -0,0 +1,122 @@
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "mcp.h"
#include "service_volume.h"
#define TAG "mcp_tool_volume"
static cJSON *volume_control_list(const char *name)
{
cJSON *tool = mcp_tool_list_info_create_default(name,
"音量控制与调节工具。当用户发出调节音量、控制声音大小的指令时调用。支持绝对值设置调到50%、相对值调节大声点、调小2档以及极值控制静音、最大声");
if (!tool) {
return NULL;
}
mcp_tool_info_add_property(tool, "text", "用户输入的关于音量调节的意图", "string", true);
cJSON *intent_property = cJSON_CreateObject();
cJSON_AddStringToObject(intent_property, "type", "string");
cJSON_AddStringToObject(intent_property, "description",
"调节类型set设定到具体值或极值调到10档、最大声、最小声adjustUp调大声、加两档adjustDown调小声、小一点");
cJSON *intent_enum = cJSON_CreateArray();
cJSON_AddItemToArray(intent_enum, cJSON_CreateString("set"));
cJSON_AddItemToArray(intent_enum, cJSON_CreateString("adjustUp"));
cJSON_AddItemToArray(intent_enum, cJSON_CreateString("adjustDown"));
cJSON_AddItemToObject(intent_property, "enum", intent_enum);
mcp_tool_info_add_json_property(tool, "intent", intent_property, true);
mcp_tool_info_add_property(tool, "value",
"具体的数值或状态1. 数字1, 10, 502. 标准状态位max最大声、满格、min最小声、静音、mid中等音量如果没有明确参数则留空大点儿声音",
"string", false);
cJSON *unit_property = cJSON_CreateObject();
cJSON_AddStringToObject(unit_property, "type", "string");
cJSON_AddStringToObject(unit_property, "description", "描述数值的单位。若指令中提及'档'、'级'、'%'则对应填写,否则留空。");
cJSON *unit_enum = cJSON_CreateArray();
cJSON_AddItemToArray(unit_enum, cJSON_CreateString(""));
cJSON_AddItemToArray(unit_enum, cJSON_CreateString(""));
cJSON_AddItemToArray(unit_enum, cJSON_CreateString("百分比"));
cJSON_AddItemToArray(unit_enum, cJSON_CreateString(""));
cJSON_AddItemToObject(unit_property, "enum", unit_enum);
mcp_tool_info_add_json_property(tool, "unit", unit_property, false);
return tool;
}
static cJSON *volume_control_call(const char *id, const char *name, cJSON *args)
{
const cJSON *text_json = mcp_tool_call_args_get(args, "text");
const cJSON *intent_json = mcp_tool_call_args_get(args, "intent");
const cJSON *value_json = mcp_tool_call_args_get(args, "value");
const cJSON *unit_json = mcp_tool_call_args_get(args, "unit");
if (!text_json || !cJSON_IsString(text_json)) {
LOGE("text parameter not found or invalid");
return NULL;
}
if (!intent_json || !cJSON_IsString(intent_json)) {
LOGE("intent parameter not found or invalid");
return NULL;
}
const char *text = text_json->valuestring;
const char *intent = intent_json->valuestring;
const char *value = value_json && cJSON_IsString(value_json) ? value_json->valuestring : NULL;
const char *unit = unit_json && cJSON_IsString(unit_json) ? unit_json->valuestring : "";
LOGI("Volume control - text: %s, intent: %s, value: %s, unit: %s",
text, intent, value ? value : "null", unit);
if (strcmp(intent, "set") == 0) {
if (value) {
if (strcmp(value, "max") == 0) {
service_volume_set(100);
} else if (strcmp(value, "min") == 0) {
service_volume_set(0);
} else if (strcmp(value, "mid") == 0) {
service_volume_set(50);
} else {
int target_vol = atoi(value);
service_volume_set(target_vol);
}
}
} else if (strcmp(intent, "adjustUp") == 0) {
int adjust_step = 10;
if (value) {
adjust_step = atoi(value);
if (adjust_step <= 0) adjust_step = 10;
}
service_volume_adjust(adjust_step);
} else if (strcmp(intent, "adjustDown") == 0) {
int adjust_step = 10;
if (value) {
adjust_step = atoi(value);
if (adjust_step <= 0) adjust_step = 10;
}
service_volume_adjust(-adjust_step);
}
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON *content_array = cJSON_CreateArray();
cJSON *content_item = cJSON_CreateObject();
cJSON_AddStringToObject(content_item, "type", "text");
cJSON_AddStringToObject(content_item, "text", "已完成操作");
cJSON_AddItemToArray(content_array, content_item);
cJSON_AddItemToObject(result, "content", content_array);
cJSON_AddBoolToObject(result, "isError", false);
return result;
}
MCP_TOOL_DEFINE(ls.set_volume, volume_control_list, volume_control_call);

165
apps/arcs-mini/memap.h Normal file
View File

@@ -0,0 +1,165 @@
#ifndef __ARCS_MEMAP_HEADER__
#define __ARCS_MEMAP_HEADER__
#define __KB__(x) ((x) * 1024)
#define __MB__(x) ((x) * 1024 * 1024)
#if CONFIG_ARCS_AP_CORE
#define MEM_ILM_BASE 0x00080000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00100000
#define MEM_DLM_SIZE (__KB__(8))
#else
#define MEM_ILM_BASE 0x00280000
#define MEM_ILM_SIZE (__KB__(16))
#define MEM_DLM_BASE 0x00300000
#define MEM_DLM_SIZE (__KB__(8))
#endif
#define MEM_BTRAM_BASE 0x200C0000
#define MEM_BTRAM_SIZE (__KB__(24))
/* FLASH 分配 */
#define MEM_TOTAL_FLASH_SIZE __MB__(16)
#define MEM_AP_FLASH_BASE 0x30040000
#define MEM_AP_FLASH_SIZE __KB__(1024)
#define MEM_CP_FLASH_BASE (0x30000000 + __MB__(6))
#define MEM_CP_FLASH_SIZE __MB__(8)
/* PSRAM 分配 */
#define MEM_AP_PSRAM_BASE 0x28000000
#define MEM_AP_PSRAM_SIZE __MB__(8)
#define MEM_CP_PSRAM_BASE ((MEM_AP_PSRAM_BASE) + (MEM_AP_PSRAM_SIZE))
#define MEM_CP_PSRAM_SIZE __MB__(8)
#if CONFIG_ARCS_AP_CORE
#define MEM_PSRAM_BASE MEM_AP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_AP_PSRAM_SIZE
#else
#define MEM_PSRAM_BASE MEM_CP_PSRAM_BASE
#define MEM_PSRAM_SIZE MEM_CP_PSRAM_SIZE
#endif
#if CONFIG_ARCS_AP_CORE
#define MEM_FLASH_BASE MEM_AP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_AP_FLASH_SIZE
#else
#define MEM_FLASH_BASE MEM_CP_FLASH_BASE
#define MEM_FLASH_SIZE MEM_CP_FLASH_SIZE
#endif
#define __MEM_TOTAL_SRAM_START__ 0x20000080 //暂留128(0x80)字节给WiFi硬件
#define __MEM_TOTAL_SRAM_END__ 0x20050000
/**
* SRAM 应用侧分配
* --------------------------------------------
* 0x20000000 | CP-WIFI-RAM(这段内存必须在 0x20000000 + 256KB区间)
* | |
* (256K) | CP-SRAM
* | |
* | | AP-WIFI-RAM (这段内存必须在 0x20000000 + 256KB区间)
* -------------- |
* 0x20040000 | 接在CP核上的SRAM(总计64K)
* (8K) | IPC共享内存
* (28K) | LUNA CODE & DATA
* -------------- |
* 0x20048800 | 接在AP核上的SRAM(总计384K)
* (64K) | AP-SRAM
* --------------------------------------------
* 0x20058800 | AP算法使用(HardCode)
* | |
* (350K) | ALGO
* | |
* --------------------------------------------
* 0x200B0000 |
* | |
* (24K) | LUNA 核专属内存
* | |
* --------------------------------------------
* 0x200C0000
*/
/* 独立给算法使用 */
#define MEM_APRAM_BASE 0x20058800
#define MEM_APRAM_SIZE (__KB__(350))
/* 注意,修改以下内存时, 必须与CP保持同步修改 */
#define MEM_CP_WIFI_RAM_BASE __MEM_TOTAL_SRAM_START__
#define MEM_CP_WIFI_RAM_SIZE (__KB__(96) - 128) //暂留128(0x80)字节给WiFi硬件
#define MEM_CP_SRAM_BASE ((MEM_CP_WIFI_RAM_BASE) + (MEM_CP_WIFI_RAM_SIZE))
#define MEM_CP_SRAM_SIZE (__KB__(160))
#define MEM_AP_WIFI_RAM_BASE ((MEM_CP_SRAM_BASE) + (MEM_CP_SRAM_SIZE))
#define MEM_AP_WIFI_RAM_SIZE (__KB__(0))
#define MEM_IPC_BASE ((MEM_AP_WIFI_RAM_BASE) + (MEM_AP_WIFI_RAM_SIZE))
#define MEM_IPC_SIZE (__KB__(8))
#define MEM_LUNA_BASE ((MEM_IPC_BASE) + (MEM_IPC_SIZE))
#define MEM_LUNA_SIZE (__KB__(28))
#define MEM_AP_SRAM_BASE ((MEM_LUNA_BASE) + (MEM_LUNA_SIZE))
#define MEM_AP_SRAM_SIZE (__KB__(62))
#if CONFIG_ARCS_CP_CORE
#define MEM_SRAM_BASE MEM_CP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_CP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_CP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_CP_WIFI_RAM_SIZE
#else
#define MEM_SRAM_BASE MEM_AP_SRAM_BASE
#define MEM_SRAM_SIZE MEM_AP_SRAM_SIZE
#define MEM_WFRAM_BASE MEM_AP_WIFI_RAM_BASE
#define MEM_WFRAM_SIZE MEM_AP_WIFI_RAM_SIZE
#endif
/* 检查下内存是否超出 */
#if (MEM_IPC_BASE + MEM_IPC_SIZE) > __MEM_TOTAL_SRAM_END__
#error "mem sram overflow"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_CP_WIFI_RAM_BASE < 0x20000000) || ((MEM_CP_WIFI_RAM_BASE + MEM_CP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "cp wifi sram error"
#endif
/* wifi sram 必须要在 0x20000000 ~ 0x20040000之间 */
#if (MEM_AP_WIFI_RAM_BASE < 0x20000000) || ((MEM_AP_WIFI_RAM_BASE + MEM_AP_WIFI_RAM_SIZE) > (0x20000000 + __KB__(256)))
#error "ap wifi sram error"
#endif
/* luna code & instruction & data 必须要在 0x20040000 ~ 0x200B0000 + 448(KB)之间 */
#if (MEM_LUNA_BASE < 0x20040000) || ((MEM_LUNA_BASE + MEM_LUNA_SIZE) > (0x20040000 + __KB__(448)))
#error "ap luna sram error"
#endif
/* 根据实际需要调整 */
#if CONFIG_ARCS_AP_CORE
/* 这个是SRAM HEAP大小的定义, 该heap从SRAM中分配 */
#define SRAM_HEAP_SIZE (__KB__(5))
// /* WIFI校准数据大小 */
// #define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
// #define MEM_WIFI_TRACE_SIZE (__KB__(1))
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(16))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
#define MEM_WIFI_LA_DUMP_SIZE (__KB__(32))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#else
/* 在CP侧, 没有wifi的校准数据与跟踪数据, 这里定义为0 */
#define MEM_WIFI_CALIBRATION_SIZE (__KB__(0))
#define MEM_WIFI_TRACE_SIZE (__KB__(0))
/* 中断栈 */
#define MEM_INTERRUPT_STACK_SIZE (4 * 1024)
#endif
#endif//__ARCS_MEMAP_HEADER__

View File

@@ -0,0 +1,8 @@
listenai_library_named(app-misc)
listenai_library_sources(
img_helper.c
)
listenai_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@@ -0,0 +1,119 @@
#define TJE_IMPLEMENTATION
#include "tiny_jpeg.h"
#include "sysheap.h"
#include "lisa_log.h"
#define TAG "img_helper"
struct jpeg_write_context {
uint8_t *buf;
uint32_t total_size;
uint32_t wrote;
};
static void jpeg_write(void *context, void *data, int size)
{
struct jpeg_write_context *ctx = (struct jpeg_write_context *)context;
if (ctx == NULL) {
return;
}
uint32_t can_write = ctx->total_size - ctx->wrote;
can_write = can_write >= size ? size : can_write;
memcpy(ctx->buf + ctx->wrote, data, can_write);
ctx->wrote += can_write;
}
int img_helper_rgb888_to_jpeg(const uint8_t *rgb_data, uint32_t width, uint32_t height, uint8_t **jpeg_data, uint32_t *jpeg_len)
{
if (!rgb_data || !jpeg_data || !jpeg_len || width == 0 || height == 0) {
return -1;
}
uint32_t jpeg_buf_size = width * height * 3 / 5;
if (jpeg_buf_size < 1024) {
jpeg_buf_size = 1024;
}
uint8_t *jpeg_buf = psram_malloc(jpeg_buf_size);
if (!jpeg_buf) {
LOGE("failed to allocate JPEG buffer");
return -1;
}
struct jpeg_write_context jpeg_write_ctx = {
.buf = jpeg_buf,
.total_size = jpeg_buf_size,
.wrote = 0,
};
int result = tje_encode_with_func(jpeg_write, &jpeg_write_ctx, 1, width, height, 3, rgb_data);
if (result == 0) {
LOGE("JPEG encoding failed");
psram_free(jpeg_buf);
return -1;
}
*jpeg_data = jpeg_buf;
*jpeg_len = jpeg_write_ctx.wrote;
return 0;
}
void img_helper_rgb565_to_rgb888(const uint16_t *rgb565, uint8_t *rgb888, uint32_t width, uint32_t height)
{
uint32_t pixel_count = width * height;
for (uint32_t i = 0; i < pixel_count; i++) {
uint16_t pixel = rgb565[i];
uint8_t r5 = (pixel >> 11) & 0x1F;
uint8_t g6 = (pixel >> 5) & 0x3F;
uint8_t b5 = pixel & 0x1F;
rgb888[i * 3 + 0] = (r5 * 255 + 15) / 31;
rgb888[i * 3 + 1] = (g6 * 255 + 31) / 63;
rgb888[i * 3 + 2] = (b5 * 255 + 15) / 31;
}
}
int img_helper_rgb565_to_jpeg(uint8_t *rgb565, uint32_t len, int width, int height, uint8_t **jpeg_data, uint32_t *jpeg_len)
{
if (!rgb565 || !jpeg_data || !jpeg_len || width <= 0 || height <= 0) {
LOGE("Invalid parameters");
return -1;
}
uint32_t pixel_count = len / 2;
uint8_t *rgb888 = psram_malloc(pixel_count * 3);
if (!rgb888) {
LOGE("Failed to allocate RGB888 buffer");
return -1;
}
img_helper_rgb565_to_rgb888((const uint16_t *)rgb565, rgb888, width, height);
uint8_t *jpeg_buf = NULL;
uint32_t jpeg_size = 0;
int ret = img_helper_rgb888_to_jpeg(rgb888, width, height, &jpeg_buf, &jpeg_size);
psram_free(rgb888);
if (ret != 0) {
LOGE("Failed to encode JPEG");
return -1;
}
*jpeg_data = jpeg_buf;
*jpeg_len = jpeg_size;
return 0;
}
void img_helper_jpeg_free(void *jpeg)
{
if (jpeg) {
psram_free(jpeg);
}
}

View File

@@ -0,0 +1,21 @@
#ifndef IMG_HELPER_H
#define IMG_HELPER_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
int img_helper_rgb888_to_jpeg(const uint8_t *rgb_data, uint32_t width, uint32_t height, uint8_t **jpeg_data,
uint32_t *jpeg_len);
void img_helper_rgb565_to_rgb888(const uint16_t *rgb565, uint8_t *rgb888, uint32_t width, uint32_t height);
int img_helper_rgb565_to_jpeg(uint8_t *rgb565, uint32_t len, int width, int height, uint8_t **jpeg_data,
uint32_t *jpeg_len);
void img_helper_jpeg_free(void *jpeg);
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load Diff

490
apps/arcs-mini/prj.conf Normal file
View File

@@ -0,0 +1,490 @@
# 项目可根据需要在此文件中设置对应的配置
CONFIG_ARCS_HAL_WARNING_DISABLE=y
CONFIG_SDK_MODULE_EASYLOGGER=y
CONFIG_EASYLOGGER_LINE_BUF_SIZE=4096
CONFIG_BOOT_HART=n
# CONFIG_ARCS_HAL_DEBUG=y
CONFIG_MODULE_FREERTOS=y
CONFIG_MODULE_HEAP=y
CONFIG_LINK_OPTION_NOSYS_SPECS=y
CONFIG_FREERTOS_STATIC_ALLOCATION_ENABLE=y
CONFIG_ARCS_CP_CORE=y
#using mbedtls for hardware decoding(IF CONFIG_WX_SQLITE3_HARD_CODEC)
CONFIG_SDK_MODULE_MBEDTLS=y
CONFIG_CUSTOM_MBEDTLS_CFG_FILE=y
CONFIG_MBEDTLS_CFG_FILE="app-config-tls-generic.h"
CONFIG_MBEDTLS_HARDWARE_ENTROPY=y
# CONFIG_MBEDTLS_HARDWARE_AES=y
# CONFIG_MBEDTLS_HARDWARE_SHA1=y
# CONFIG_MBEDTLS_HARDWARE_SHA256=y
# CONFIG_MBEDTLS_HARDWARE_SHA512=y
# CONFIG_MBEDTLS_HARDWARE_AES_CCM=y
# CONFIG_MBEDTLS_HARDWARE_AES_GCM=y
# CONFIG_MBEDTLS_HARDWARE_ECC=y
# System support
CONFIG_MBEDTLS_HAVE_ASM=y
CONFIG_MBEDTLS_HAVE_TIME_DATE=y
CONFIG_MBEDTLS_SSL_ALPN=y
# Save RAM at the expense of ROM
CONFIG_MBEDTLS_AES_ROM_TABLES=y
# mbed TLS feature support
CONFIG_MBEDTLS_CIPHER_MODE_CBC_ENABLED=y
CONFIG_MBEDTLS_KEY_EXCHANGE_RSA_ENABLED=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED=y
# Supported TLS versions
CONFIG_MBEDTLS_TLS_VERSION_1_0=y
CONFIG_MBEDTLS_TLS_VERSION_1_1=y
CONFIG_MBEDTLS_TLS_VERSION_1_2=y
# mbed TLS modules
CONFIG_MBEDTLS_CIPHER_AES_ENABLED=y
CONFIG_MBEDTLS_CIPHER=y
CONFIG_MBEDTLS_CTR_DRBG_ENABLED=y
CONFIG_MBEDTLS_ENTROPY_ENABLED=y
CONFIG_MBEDTLS_MD=y
CONFIG_MBEDTLS_MAC_MD5_ENABLED=y
CONFIG_MBEDTLS_HASH_MD5_ENABLED=y
CONFIG_MBEDTLS_MAC_SHA1_ENABLED=y
CONFIG_MBEDTLS_HASH_SHA1_ENABLED=y
CONFIG_MBEDTLS_MAC_SHA256_ENABLED=y
CONFIG_MBEDTLS_SHA256_SMALLER=y
CONFIG_MBEDTLS_HASH_SHA256_ENABLED=y
CONFIG_MBEDTLS_DTLS=y
CONFIG_MBEDTLS_SERVER_NAME_INDICATION=y
CONFIG_MBEDTLS_ECDH_C=y
CONFIG_MBEDTLS_ECP_C=y
CONFIG_MBEDTLS_CIPHER_GCM_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y
CONFIG_MBEDTLS_MAC_SHA512_ENABLED=y
CONFIG_MBEDTLS_HASH_SHA512_ENABLED=y
CONFIG_MBEDTLS_PEM_CERTIFICATE_FORMAT=y
CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384
CONFIG_MBEDTLS_CTR_DRBG_ENABLED=y
# CONFIG_MBEDTLS_DEBUG=y
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=n
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
CONFIG_LWIP=y
CONFIG_WIFI=y
CONFIG_ARCS_HAL_WLIF=y
CONFIG_ARCS_HAL_EVENT=y
CONFIG_ARCS_HAL_CLI_CMD=y
CONFIG_ARCS_HAL_UTILS=y
CONFIG_MODULE_ZE_TLS=y
CONFIG_MODULE_HTTPCLIENT=y
CONFIG_MODULE_NOPOLL=y
CONFIG_MODULE_CORESNTP=y
CONFIG_LWIP_OPTION_SO_REUSE=y
CONFIG_LWIP_OPTION_LWIP_DNS=y
CONFIG_MEM_CONFIG=n
CONFIG_MEM_CONFIG_USE_CUSTOM_FILE=y
CONFIG_MEM_CONFIG=n
CONFIG_MEM_CONFIG_USE_CUSTOM_FILE=y
CONFIG_HEAP_SIZE=0x5e00
CONFIG_BACK_TRACE=y
CONFIG_PSRAM_HEAP_SIZE=0x600000
CONFIG_DCACHE_ENABLE=y
# 使能collection-c
CONFIG_SDK_MODULE_COLLECTIONS_C=y
CONFIG_CORE_SPINLOCK=n
CONFIG_CJSON=y
CONFIG_SDK_MODULE_EASYFLASH=y
CONFIG_SDK_MODULE_LETTER_SHELL=y
CONFIG_SDK_MODULE_LETTER_SHELL_CONFIG_FILE="shell_cfg_user.h"
CONFIG_TINY_USB=y
CONFIG_TINY_USB_USE_CUSTOM_CONFIG_FILE=y
CONFIG_TINY_USB_CONFIG_FILE="tusb_config.h"
CONFIG_APP_USB_AUDIO_ENABLE=y
# 文件系统配置
# CONFIG_FILE_SYSTEM=y
# CONFIG_FATFS_FILESYSTEM=y
# DISK驱动使能
CONFIG_DISK_DRIVER=y
# DISK SDMMC驱动使能及配置
CONFIG_DISK_DRIVER_SDMMC=y
# DISK FLASH驱动使能及配置
CONFIG_DISK_DRIVER_FLASH=y
CONFIG_DISK_FLASH_START_ADDR=0xF00000
CONFIG_DISK_FLASH_MAX_RW_SIZE=4096
CONFIG_DISK_FLASH_ERASE_ALIGNMENT=4096
CONFIG_DISK_FLASH_ERASE_BLOCK_SIZE=4096
CONFIG_DISK_FLASH_SECTOR_SIZE=4096
CONFIG_DISK_FLASH_VOLUME_SIZE=1048576
# 抽象文件系统
CONFIG_LSFS=y
CONFIG_LSFS_FAT=y
CONFIG_TUSB_APP_CLASS=y
CONFIG_ADB_SYNC=y
CONFIG_ADB_SHELL=y
CONFIG_ADB_SHELL_BUFFER_SIZE=2048
CONFIG_ADB_PUSH_PULL_DEFAULT_ROOT="/SD:/adb/"
# DISK驱动使能
CONFIG_DISK_DRIVER=y
# 抽象文件系统
CONFIG_LSFS=y
CONFIG_LSFS_FAT=y
CONFIG_FS_ENV_MEMORY_MANAGEMENT_USE_PSRAM=y
#WIFI MANAGER
CONFIG_WIFI_MANAGER=y
CONFIG_LISA_PORTING=y
CONFIG_EASYLOGGER_USE_POSIX_TIME=y
CONFIG_SDK_MODULE_COREHTTP=y
CONFIG_SDK_MODULE_SPEEXDSP=y
CONFIG_SDK_MODULE_MEMPOOL=y
CONFIG_SDK_MODULE_CURL=n
CONFIG_WIFI_MGR_NVS_PARTITION_OFFSET=0xff0000
CONFIG_LISA_DISPLAY=y
CONFIG_LISA_DISPLAY_ST7789P3=y
CONFIG_LISA_DISPLAY_TE_SYNC=y
CONFIG_LISA_DISPLAY_CPDMA_ROTATE=y
CONFIG_LISA_DISPLAY_INTERFACE_SPI_4_LINE=y
CONFIG_DISPLAY_ST7789P3_WIDTH=240
CONFIG_DISPLAY_ST7789P3_HEIGHT=240
CONFIG_DISPLAY_ST7789P3_X_OFFSET=0
CONFIG_LISA_TOUCH=y
CONFIG_LISA_TOUCH_CST328=y
# CONFIG_SDK_MODULE_LVGL8=y
# CONFIG_LV_DEMO_SYS_HEAP=y
# CONFIG_LV_DRIVER_PIXEL_ALIGN_SIZE=16
# CONFIG_LV_USE_DEMO_WIDGETS=y
# CONFIG_LV_USE_GPU_CSK_DMA2D=y
# CONFIG_LV_DOUBLE_VDB=y
# CONFIG_LV_USE_PERF_MONITOR=n
# CONFIG_LV_DRIVER_FULL_REFRESH=y
# CONFIG_LV_CUSTOM_MONITOR=n
# CONFIG_LV_REFR_UNCONDITIONAL_AREA_JOIN=y
# CONFIG_LV_DRIVER_ROTATE_90=y
# CONFIG_LV_CONF_SKIP=n
# CONFIG_LV_USE_RPC=n
# CONFIG_LV_USE_RPC_THRESHOLD=8000
# CONFIG_LV_CUSTOM_TASK_CPU_PER=n
# CONFIG_LV_IMG_CACHE_DEF_SIZE=30
# CONFIG_LS_LV_INDEV_TASK_FILTERING=y
# CONFIG_LS_LV_INDEV_TASK_PRIO=5
# CONFIG_LS_LV_INDEV_TASK_STACK_SIZE=1024
# CONFIG_LS_LV_INDEV_TASK_QUEUE_SIZE=4
# CONFIG_LV_INDEV_DEF_SCROLL_THROW=4
# CONFIG_LV_FONT_FMT_TXT_LARGE=y
# CONFIG_EASYLOGGER_LOG_LEVEL_DEBUG=y
CONFIG_ARCS_HAL_LSF=y
CONFIG_IPC_LSF=y
CONFIG_LSF_CLIENT=y
CONFIG_WIFI_MANAGER=y
CONFIG_MAC_MANAGER=y
CONFIG_MAC_MANAGER_MAC_BY_CHIP_ID=y
CONFIG_LISA_TOUCH_I2C_SPINLOCK=n
CONFIG_SDK_MODULE_MP3_DECODER=y
CONFIG_SDK_MODULE_AAC_DECODER=y
CONFIG_ARCS_HAL_DCACHE_COHERENT=n
CONFIG_FREERTOS_FAST_FUNC_SECTION=".text"
CONFIG_LISA_PORTING=y
CONFIG_LISA_OS=y
CONFIG_LISA_KV=y
CONFIG_LISA_NETWORK=y
CONFIG_SDK_MODULE_SPEEXDSP=n
CONFIG_SDK_MODULE_LISA_PLAYER=y
# CONFIG_APP_PLAYER is not set
# CONFIG_APP_PLAYER_AUDIO_FOCUS is not set
CONFIG_SDK_MODULE_RESAMPLE=n
CONFIG_SDK_MODULE_SPEEXDSP=y
CONFIG_LISA_PORTING=y
CONFIG_LOG=y
CONFIG_DISK_MEM=n
CONFIG_DISK_MEM_SERVER=n
CONFIG_LISA_DISPLAY_INTERFACE_SPI_4_LINES=y
CONFIG_BOOT=n
CONFIG_4G_MODULE=n
CONFIG_LWIP=y
CONFIG_WIFI=y
CONFIG_WIFI_LWIP_SAME_CORE=y
CONFIG_LWIP_OPTION_LWIP_DNS=y
CONFIG_LWIP_OPTION_SO_REUSE=y
CONFIG_LOW_WIFI_CLI=y
CONFIG_ARCS_HAL_WCND_GPADC_IC_MUTEX=n
CONFIG_ARCS_HAL_IC_MUTEX=n
# CONFIG_LISAUI=y
CONFIG_EBUS=y
# camera
CONFIG_COMPONENT_CAMERA=y
CONFIG_CAMERA_INTERFACE_DVP=y
CONFIG_BF3901_SUPPORT=n
CONFIG_GC0328_SUPPORT=y
#GPDMA
# CONFIG_ARCS_GPDMA_DATA_ONLY=y
CONFIG_ARCS_GPDMA_FULL_INIT=y
CONFIG_LWIP_TX_BUF_COPY=y
CONFIG_WIFI_TX_BUF_COPY=y
CONFIG_ARCS_HAL_WCND_RF_SELFCALI_FROM_NV=n
CONFIG_LISA_FLASH_MSG=y
CONFIG_BATTERY_COLLECTION=y
CONFIG_FLEXIBLE_BUTTON=y
CONFIG_ARCS_HAL_IPC=y
CONFIG_ARCS_HAL_FLASH_IF=y
CONFIG_ARCS_HAL_IPC_FLASH_AGENT=y
CONFIG_ARCS_HAL_IPC_HALT_PEER_CORE=y
CONFIG_ARCS_HAL_NVS=y
CONFIG_BLE=y
CONFIG_BLE_PROFILE_HOGPD=y
CONFIG_BLE_PROFILE_NETCFG_BLES=y
CONFIG_BLE_PROFILE_BASS=y
CONFIG_LISA_KV_TYPE_EF=y
CONFIG_ADB_SYNC=n
CONFIG_SYSLOG_PRINTK_REDIRECT=y
CONFIG_SYSLOG_PRINTF_REDIRECT=y
CONFIG_COMPILE_OPTION_WARNING_AS_ERROR=y
CONFIG_LISA_DISPLAY_COLOR_INVERT=y
CONFIG_SDK_MODULE_ICO=y
CONFIG_BOARDS_CHOICE_ARCS_MINI=y
CONFIG_LOG_MODE_ASYNC=y
CONFIG_EASYLOGGER_LOG_MODE_ASYNC=y
# acomp
CONFIG_ACOMP=y
CONFIG_ACOMP_WAKEUP=y
# CONFIG_ACOMP_WAKEUP_AUDIO_IN_DUAL_MIC=y
# CONFIG_ACOMP_WAKEUP_RES_CAE_MLP_ADDRESS=0x30200000
# CONFIG_ACOMP_WAKEUP_RES_CAE_MLP_LENGTH=1182080
# CONFIG_ACOMP_WAKEUP_RES_ESR_MLP_ADDRESS=0x30330000
# CONFIG_ACOMP_WAKEUP_RES_ESR_MLP_LENGTH=1198464
# CONFIG_ACOMP_WAKEUP_RES_ESR_MAIN_ADDRESS=0x30460000
# CONFIG_ACOMP_WAKEUP_RES_ESR_MAIN_LENGTH=608
# CONFIG_ACOMP_WAKEUP_RES_ESR_CMDS_ADDRESS=0x30470000
# CONFIG_ACOMP_WAKEUP_RES_ESR_CMDS_LENGTH=11248
# CONFIG_ACOMP_WAKEUP_RES_CAE_WRAP_ADDRESS=0x30480000
# CONFIG_ACOMP_WAKEUP_RES_CAE_WRAP_LENGTH=262
# CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS=0x30490000
# CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH=395
CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_SINGLE_MIC=y
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_ADDRESS=0x30200000
CONFIG_ACOMP_WAKEUP_RES_CAE_ESR_MLP_LENGTH=1431296
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_ADDRESS=0x304D0000
CONFIG_ACOMP_WAKEUP_RES_AI_WRAP_LENGTH=763
CONFIG_BOOT_WITH_WATCHDOG=y
CONFIG_WATCHDOG_ENABLE=n
CONFIG_LITE_ADC=y
CONFIG_AADC_LR_MIX=y
CONFIG_LITE_AADC_RECV_CNTS=50
CONFIG_ECHO_RECV_CNTS=50
CONFIG_LITE_DAC=y
CONFIG_DAC_ECHO_ENABLE=n
CONFIG_SYSLOG_PRINTF_REDIRECT=n
CONFIG_CLOUD_PRODUCT_ID_DEFAULT="cf75e7a9-66b6-41e3-918a-141800aebb5b"
CONFIG_CLOUD_SECRET_ID_DEFAULT="e354f1f0-34e5-488f-8ef2-f6db2a44c815"
CONFIG_CLOUD_FULL_DUPLEX_ENABLE=y
CONFIG_CLOUD_FULL_DUPLEX_TIMEOUT_MS=180000
CONFIG_CLOUD_IDLE_EXIT_TIMEOUT_MS=10000
CONFIG_CLOUD_STAGING_MODE_ENABLE=n
# CONFIG_BOARDS_CHOICE_CUSTOM=y
CONFIG_ACOMP_LOGGER=y
CONFIG_LS_CHAT_MCP_V2=y
CONFIG_LISA_WEBSOCKET_PING_PONG_MSG=n
CONFIG_NOPOLL_DELIVER_PONG_FRAME_ENABLED=n
#server
# SDK 切换
# CONFIG_ARCS_USE_LINGXIN_SDK is not set
CONFIG_LINGXIN_CLOUD_HOST="eagent.edu-aliyun.com"
CONFIG_LINGXIN_CLOUD_PORT=80
# CONFIG_LINGXIN_ENABLE_SCHEDULE_TASK is not set
# CONFIG_LINGXIN_ENABLE_LOG_UPLOAD is not set
CONFIG_MCP_TOOL_EMOJI_ENABLE=y
CONFIG_MCP_TOOL_CAMERA_ENABLE=y
CONFIG_LISA_SHELL=y
CONFIG_SYSLOG_UART_BACKEND=y
# LISA UART Driver
CONFIG_LISA_DEVICE=y
CONFIG_LISA_UART_DEVICE=y
CONFIG_LISA_UART0=y
CONFIG_LISA_UART1=y
CONFIG_LISA_UART2=y
CONFIG_LISA_WEBSOCKET=y
CONFIG_LISA_HTTP=y
CONFIG_WIFI_MANAGER=y
CONFIG_LISA_FLASH_ARCS_HALT_REMOTE_CORE=y
CONFIG_PA_PORT=0
CONFIG_PA_PIN=27
CONFIG_PA_FUNC=0
CONFIG_CATEGORY_MINI=y
CONFIG_ACOMP_WAKEUP_ALGORITHM_TYPE_DUAL_MIC=n
# LISA Audio Driver
CONFIG_LISA_AUDIO_DEVICE=y
CONFIG_LISA_AUDIO_RECORD_INDIVIDUAL_GAIN=y
CONFIG_LISA_AUDIO_PLAY_ECHO_ENABLE=y
CONFIG_LISA_AUDIO_PLAY_PA_ENABLE=n
#初始化gpio
CONFIG_LISA_GPIO_DEVICE=y
CONFIG_LISA_GPIOB=y
CONFIG_LISA_DEVICE=y
CONFIG_LISA_GPIO_DEVICE=y
CONFIG_LISA_GPIOA=y
CONFIG_LISA_GPIOB=y
CONFIG_LISA_PWM=y
CONFIG_LISA_SPI1=y
CONFIG_LISA_DISPLAY_DEVICE=y
CONFIG_LISA_DISPLAY_PANEL_ST7789P3=y
CONFIG_LISA_DISPLAY_TE_SYNC=y
CONFIG_LISA_DISPLAY_CPDMA_ROTATE=y
CONFIG_LISA_DISPLAY_BUS_SPI_4WIRE=y
CONFIG_PANEL_ST7789P3_WIDTH=240
CONFIG_PANEL_ST7789P3_HEIGHT=240
CONFIG_PANEL_ST7789P3_X_OFFSET=0
CONFIG_LISA_TOUCH_DEVICE=y
CONFIG_LISA_I2C=y
CONFIG_LISA_I2C0=y
CONFIG_LISA_TOUCH_ARCS_CST328=y
CONFIG_SDK_MODULE_LVGL8=y
CONFIG_LV_DEMO_SYS_HEAP=y
CONFIG_LV_DRIVER_PIXEL_ALIGN_SIZE=16
CONFIG_LV_USE_DEMO_WIDGETS=y
CONFIG_LV_USE_GPU_CSK_DMA2D=y
CONFIG_LV_DOUBLE_VDB=y
CONFIG_LV_USE_PERF_MONITOR=n
CONFIG_LV_DRIVER_FULL_REFRESH=n
CONFIG_LV_CUSTOM_MONITOR=n
CONFIG_LV_REFR_UNCONDITIONAL_AREA_JOIN=n
CONFIG_LV_DRIVER_ROTATE_90=y
CONFIG_LISA_DISPLAY_DEVICE=y
CONFIG_LV_CONF_SKIP=y
CONFIG_SDK_MODULE_LIBPNG=y
CONFIG_SDK_MODULE_IJG=y
CONFIG_LVGL_USE_INDEPENDENT_HEAP=y
CONFIG_LVGL_HEAP_SIZE=0x100000
CONFIG_LISA_DISPLAY_COLOR_INVERT=y
CONFIG_LV_COLOR_DEPTH=16
CONFIG_LV_USE_PNG=y
CONFIG_LV_USE_BMP=y
CONFIG_LV_USE_SJPG=y
CONFIG_LISAUI=y
CONFIG_LV_USE_FONT_PLACEHOLDER=n
CONFIG_FILE_SYSTEM=y
CONFIG_LSFS=y
CONFIG_LSFS_FAT=y
CONFIG_FS_ENV_MEMORY_MANAGEMENT_USE_PSRAM=y
CONFIG_LISA_ADC=y
CONFIG_FLEXIBLE_BUTTON=y
CONFIG_APPLICATION_UI=y
CONFIG_LISA_I2C=y
CONFIG_LISA_I2C0=y
CONFIG_LISA_SPI_DEVICE=y
CONFIG_LISA_SPI0=y
CONFIG_LISA_UART_DEVICE=y
CONFIG_LISA_UART_ASYNC_API=y
CONFIG_LISA_UART1=y
CONFIG_LISA_CAMERA_DEVICE=y
CONFIG_LISA_CAMERA_BUS_DVP=y
CONFIG_LISA_CAMERA_SENSOR_GC032A=y
CONFIG_LISA_CAMERA_SENSOR_GC0328=y
CONFIG_MIDDLEWARE_TONE=n
CONFIG_LISA_SHELL_TASK_PRIORITY=10
CONFIG_LISA_WIFI=y
CONFIG_LISA_TOUCH_ARCS_CST328_READ_TASK_PRIORITY=8
CONFIG_LISA_WEBSOCKET_THREAD_PRIO=9
# Lisa Bluetooth
CONFIG_BLE=y
CONFIG_LISA_BLUETOOTH=y
CONFIG_BLE_PROFILE_HOGPD=y
CONFIG_BLE_PROFILE_NETCFG_BLES=y
CONFIG_BLE_PROFILE_BASS=y
CONFIG_LWIP_NETAL_TX_SMALL_BUF_CNT=40
CONFIG_LWIP_NETAL_TX_BUF_CNT=15
CONFIG_POWER_MANAGER=y
CONFIG_POWER_MANAGER_POWER_EN_GPIO_PAD="gpiob"
CONFIG_POWER_MANAGER_POWER_KEY_GPIO_PAD="gpiob"
CONFIG_POWER_MANAGER_USB_DETECT_GPIO_PAD="gpiob"
CONFIG_BATTERY_COLLECTION=y
CONFIG_BATTERY_COLLECTION_ADC_PAD="gpiob"
CONFIG_BATTERY_COLLECTION_ADC_CHANNEL=3
CONFIG_BATTERY_COLLECTION_CHARGE_DETECT_PAD="gpiob"
CONFIG_OTA=y
CONFIG_AUDIO_DEFAULT_MIC_GAIN_A_DB=36
CONFIG_AUDIO_DEFAULT_MIC_GAIN_D_DB=0
CONFIG_AUDIO_DEFAULT_REF_GAIN_A_DB=22
CONFIG_AUDIO_DEFAULT_REF_GAIN_D_DB=0
CONFIG_AUDIO_DEFAULT_SPK_GAIN_A_DB=0
CONFIG_AUDIO_DEFAULT_SPK_GAIN_D_DB=-19

View File

@@ -0,0 +1,14 @@
listenai_library_named(app-services)
listenai_library_sources(
service_led.c
service_brightness.c
service_volume.c
service_button.c
service_camera.c
service_alarm.c
service_image.c
)
listenai_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@@ -0,0 +1,241 @@
#include "service_alarm.h"
#include "alarm.h"
#include "alarm_store.h"
#include "lisa_log.h"
#include "lisa_mem.h"
#include "voice_msg.h"
#include <string.h>
#include <stdio.h>
#define TAG "service_alarm"
static uint8_t alarm_init_done = 0;
static void service_alarm_set_err(char *err_msg, size_t err_len, const char *text)
{
if (!err_msg || err_len == 0 || !text) {
return;
}
snprintf(err_msg, err_len, "%s", text);
}
static void service_alarm_publish_create(uint64_t timestamp, const uint8_t *text)
{
struct service_alarm alarm = {0};
alarm.timestamp = timestamp;
if (text && text[0] != '\0') {
int cpy_len = strlen((const char *)text) > sizeof(alarm.text) - 1 ? sizeof(alarm.text) - 1 : strlen((const char *)text);
memcpy(alarm.text, text, cpy_len);
}
voice_msg_pub(VOICE_MSG_ALARM_CREATE, &alarm, sizeof(struct service_alarm));
}
static void service_alarm_publish_delete(uint64_t timestamp)
{
struct service_alarm alarm = {0};
alarm.timestamp = timestamp;
voice_msg_pub(VOICE_MSG_ALARM_DELETE, &alarm, sizeof(struct service_alarm));
}
int service_alarm_create_obj(const alarm_object_t *alarm, char *err_msg, size_t err_len)
{
if (!alarm) {
service_alarm_set_err(err_msg, err_len, "闹钟参数无效");
return -1;
}
int ret = alarm_store_create_obj(alarm, err_msg, err_len);
if (ret != 0) {
return -1;
}
uint64_t timestamp = alarm_obj_to_timestamp(alarm);
if (timestamp == 0) {
service_alarm_set_err(err_msg, err_len, "闹钟时间无效");
alarm_store_delete_obj(alarm);
return -1;
}
ret = ls_alarm_insert_by_timestamp(timestamp, (const uint8_t *)alarm->text);
if (ret != 0) {
alarm_store_delete_obj(alarm);
service_alarm_set_err(err_msg, err_len, "创建闹钟失败,操作闹钟链表时出现异常");
return -1;
}
service_alarm_publish_create(timestamp, (const uint8_t *)alarm->text);
return 0;
}
const alarm_object_t *service_alarm_find_by_cloud_id(uint64_t cloud_id)
{
return alarm_store_find_by_cloud_id(cloud_id);
}
int service_alarm_delete_by_cloud_id(uint64_t cloud_id, char *err_msg, size_t err_len)
{
const alarm_object_t *target = alarm_store_find_by_cloud_id(cloud_id);
if (!target) {
service_alarm_set_err(err_msg, err_len, "未找到对应闹钟");
return -1;
}
alarm_object_t backup = *target;
if (alarm_store_delete_obj(&backup) != 0) {
service_alarm_set_err(err_msg, err_len, "删除闹钟失败,清除nvs时出现异常");
return -1;
}
if (ls_alarm_delete_by_timestamp(backup.alarm_id) != 0) {
service_alarm_set_err(err_msg, err_len, "删除闹钟失败,操作闹钟链表时出现异常");
return -1;
}
service_alarm_publish_delete(backup.alarm_id);
return 0;
}
static void alarm_event_handler(uint64_t timestamp, const uint8_t *text)
{
struct service_alarm alarm = {0};
alarm.timestamp = timestamp;
int cpy_len = strlen(text) > sizeof(alarm.text) - 1 ? sizeof(alarm.text) - 1 : strlen(text);
memcpy(alarm.text, text, cpy_len);
LOGI("service alarm, alarm triger, timestamp: %llu", timestamp);
voice_msg_pub(VOICE_MSG_ALARM_TRIGGER, &alarm, sizeof(struct service_alarm));
}
void service_alarm_init(void)
{
if (alarm_init_done) {
return;
}
ls_alarm_init(alarm_event_handler);
alarm_init_done = 1;
uint32_t cnt = (uint32_t)ls_alarm_count_get();
voice_msg_pub(VOICE_MSG_ALARM_QUERY, &cnt, sizeof(cnt));
LOGI("service_alarm_init publish alarm count: %u", (unsigned int)cnt);
}
uint8_t service_alarm_init_done(void)
{
return alarm_init_done;
}
int service_alarm_add(uint64_t timestamp, const uint8_t *text)
{
int ret = ls_alarm_insert_by_timestamp(timestamp, text);
if (ret == 0) {
LOGI("Alarm added - timestamp: %llu", (unsigned long long)timestamp);
struct service_alarm alarm = {0};
alarm.timestamp = timestamp;
int cpy_len = strlen(text) > sizeof(alarm.text) - 1 ? sizeof(alarm.text) - 1 : strlen(text);
memcpy(alarm.text, text, cpy_len);
voice_msg_pub(VOICE_MSG_ALARM_CREATE, &alarm, sizeof(struct service_alarm));
} else {
LOGE("Failed to add alarm - timestamp: %llu, ret: %d", (unsigned long long)timestamp, ret);
}
return ret;
}
int service_alarm_delete(uint64_t timestamp)
{
int ret = ls_alarm_delete_by_timestamp(timestamp);
if (ret == 0) {
LOGI("Alarm added - timestamp: %llu", (unsigned long long)timestamp);
struct service_alarm alarm = {0};
alarm.timestamp = timestamp;
voice_msg_pub(VOICE_MSG_ALARM_DELETE, &alarm, sizeof(struct service_alarm));
} else {
LOGE("Failed to delete alarm - timestamp: %llu, ret: %d", (unsigned long long)timestamp, ret);
}
return ret;
}
int service_alarm_get_count(void)
{
return ls_alarm_count_get();
}
int service_alarm_delete_all(uint32_t cnt)
{
struct ls_alarm *alarm_node = ls_alarm_get();
int deleted_count = 0;
int failed_count = 0;
// Iterate through all alarms and delete them
while (alarm_node != NULL) {
uint64_t timestamp = alarm_node->timestamp;
// Move to next before deleting current node
alarm_node = alarm_node->next;
int ret = ls_alarm_delete_by_timestamp(timestamp);
if (ret == 0) {
deleted_count++;
} else {
failed_count++;
LOGE("Failed to delete alarm - timestamp: %llu", (unsigned long long)timestamp);
}
}
if (deleted_count > 0) {
LOGI("Deleted %d alarm(s)", deleted_count);
}
if (failed_count > 0) {
LOGW("Failed to delete %d alarm(s)", failed_count);
return -1;
}
return 0;
}
struct service_alarm *service_alarm_get_all(uint32_t *cnt)
{
if (cnt == NULL) {
return NULL;
}
// Get the alarm count
int alarm_count = ls_alarm_count_get();
*cnt = alarm_count;
if (alarm_count == 0) {
return NULL;
}
// Allocate memory for the service_alarm array
struct service_alarm *alarms = lisa_mem_alloc(sizeof(struct service_alarm) * alarm_count);
if (alarms == NULL) {
LOGE("Failed to allocate memory for alarms array");
*cnt = 0;
return NULL;
}
// Get the alarm linked list head
struct ls_alarm *alarm_node = ls_alarm_get();
// Convert linked list to array
int i = 0;
while (alarm_node != NULL && i < alarm_count) {
alarms[i].timestamp = alarm_node->timestamp;
memset(alarms[i].text, 0, sizeof(alarms[i].text));
int cpy_len = strlen(alarm_node->text) > sizeof(alarms[i].text) - 1
? sizeof(alarms[i].text) - 1
: strlen(alarm_node->text);
memcpy(alarms[i].text, alarm_node->text, cpy_len);
alarm_node = alarm_node->next;
i++;
}
return alarms;
}

View File

@@ -0,0 +1,25 @@
#ifndef SERVICE_ALARM_H
#define SERVICE_ALARM_H
#include <stdint.h>
#include <stddef.h>
#include "alarm_store.h"
struct service_alarm {
uint64_t timestamp;
uint8_t text[128];
};
int service_alarm_create_obj(const alarm_object_t *alarm, char *err_msg, size_t err_len);
int service_alarm_delete_by_cloud_id(uint64_t cloud_id, char *err_msg, size_t err_len);
const alarm_object_t *service_alarm_find_by_cloud_id(uint64_t cloud_id);
void service_alarm_init(void);
int service_alarm_add(uint64_t timestamp, const uint8_t *text);
int service_alarm_delete(uint64_t timestamp);
int service_alarm_get_count(void);
int service_alarm_delete_all(uint32_t cnt);
uint8_t service_alarm_init_done(void);
struct service_alarm *service_alarm_get_all(uint32_t *cnt);
#endif

View File

@@ -0,0 +1,113 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define TAG "brightness"
#include "lisa_log.h"
#include "lisa_kv.h"
#include "lisa_display.h"
#include "kv.h"
#include "IOMuxManager.h"
#include "Driver_I2C.h"
#include "board.h"
#define DEFAULT_BRIGHTNESS 70
void service_brightness_set(int brightness);
static lisa_device_t *s_display_device = NULL;
void service_brightness_init(void)
{
s_display_device = lisa_device_get("display");
if (!lisa_device_ready(s_display_device)) {
LOGE("Display device not ready");
s_display_device = NULL;
return;
}
lisa_device_t *gpioa_dev = lisa_device_get("gpioa");
lisa_device_t *gpiob_dev = lisa_device_get("gpiob");
#ifdef CONFIG_LISA_DISPLAY_COMPOSITE
board_display_composite_init();
#endif
lisa_display_config_t display_config = {
.bus_type = LISA_DISPLAY_BUS_SPI_4WIRE,
.bus_config =
{
.spi_4wire =
{
.spi_dev = lisa_device_get("spi0"),
#ifndef CONFIG_LISA_DISPLAY_COMPOSITE
.cs_gpio = gpioa_dev,
.cs_pin = LCD_CS_PIN,
#endif
.dc_gpio = gpioa_dev,
.dc_pin = LCD_CD_PIN,
.spi_freq = 50 * 1000 * 1000,
},
},
.backlight =
{
.type = LISA_DISPLAY_BACKLIGHT_TYPE_PWM,
.config.pwm =
{
.channel = 1,
.dev = lisa_device_get("pwm0"),
.freq = 2000,
},
},
.rst_gpio = gpiob_dev,
.rst_pin = LCD_RST_PIN,
#ifdef CONFIG_LISA_DISPLAY_COMPOSITE
.composite_activate = board_display_composite_activate,
.composite_deactivate = board_display_composite_deactivate,
#endif
};
lisa_display_attach_bus(s_display_device, &display_config);
int brightness = DEFAULT_BRIGHTNESS;
if (lisa_kv_get_int(KV_KEY_USER_BRIGHTNESS, &brightness) != 0) {
brightness = DEFAULT_BRIGHTNESS;
}
lisa_display_blanking_on(s_display_device);
service_brightness_set(brightness);
LOGI("Brightness service initialized");
}
void service_brightness_set(int brightness)
{
if (brightness < 0) {
brightness = 0;
}
if (brightness > 100) {
brightness = 100;
}
lisa_kv_set_int(KV_KEY_USER_BRIGHTNESS, brightness);
if (s_display_device) {
int hw_brightness = brightness;
lisa_display_set_brightness(s_display_device, hw_brightness);
LOGI("Brightness set to %d (hw: %d)", brightness, hw_brightness);
} else {
LOGW("Display device not available");
}
}
int service_brightness_get(void)
{
int brightness = DEFAULT_BRIGHTNESS;
if (lisa_kv_get_int(KV_KEY_USER_BRIGHTNESS, &brightness) != 0) {
brightness = DEFAULT_BRIGHTNESS;
}
return brightness;
}

View File

@@ -0,0 +1,10 @@
#ifndef SERVICE_BRIGHTNESS_H
#define SERVICE_BRIGHTNESS_H
#include <stdint.h>
void service_brightness_init(void);
void service_brightness_set(int brightness);
int service_brightness_get(void);
#endif

View File

@@ -0,0 +1,163 @@
#include <stdint.h>
#include <stdbool.h>
#include "lisa_log.h"
#include "lisa_btn.h"
#include "voice_msg.h"
#include "service_button.h"
#include "Driver_GPADC.h"
#include "IOMuxManager.h"
#include "board.h"
#define TAG "service_button"
extern void factory_reset(void);
static voice_msg_button_action_t map_lisa_btn_event(lisa_btn_event_t lisa_event)
{
switch (lisa_event) {
case LISA_BTN_PRESS_DOWN:
return VOICE_MSG_BUTTON_ACTION_PRESS_DOWN;
case LISA_BTN_PRESS_CLICK:
return VOICE_MSG_BUTTON_ACTION_CLICK;
case LISA_BTN_PRESS_DOUBLE_CLICK:
return VOICE_MSG_BUTTON_ACTION_DOUBLE_CLICK;
case LISA_BTN_PRESS_TRIPLE_CLICK:
return VOICE_MSG_BUTTON_ACTION_TRIPLE_CLICK;
case LISA_BTN_PRESS_QUADRUPLE_CLICK:
return VOICE_MSG_BUTTON_ACTION_QUADRUPLE_CLICK;
case LISA_BTN_PRESS_QUINTUPLE_CLICK:
return VOICE_MSG_BUTTON_ACTION_QUINTUPLE_CLICK;
case LISA_BTN_PRESS_SEXTUPLE_CLICK:
return VOICE_MSG_BUTTON_ACTION_SEXTUPLE_CLICK;
case LISA_BTN_PRESS_SEPTUPLE_CLICK:
return VOICE_MSG_BUTTON_ACTION_SEPTUPLE_CLICK;
case LISA_BTN_PRESS_REPEAT_CLICK:
return VOICE_MSG_BUTTON_ACTION_REPEAT_CLICK;
case LISA_BTN_PRESS_SHORT_START:
return VOICE_MSG_BUTTON_ACTION_SHORT_START;
case LISA_BTN_PRESS_SHORT_UP:
return VOICE_MSG_BUTTON_ACTION_SHORT_UP;
case LISA_BTN_PRESS_LONG_START:
return VOICE_MSG_BUTTON_ACTION_LONG_START;
case LISA_BTN_PRESS_LONG_UP:
return VOICE_MSG_BUTTON_ACTION_LONG_UP;
case LISA_BTN_PRESS_LONG_HOLD:
return VOICE_MSG_BUTTON_ACTION_LONG_HOLD;
case LISA_BTN_PRESS_LONG_HOLD_UP:
return VOICE_MSG_BUTTON_ACTION_LONG_HOLD_UP;
default:
return VOICE_MSG_BUTTON_ACTION_UNKNOWN;
}
}
static void publish_button_event(uint8_t btn_id, lisa_btn_event_t action)
{
voice_msg_button_evt_t evt = {
.button_id = btn_id,
.action = map_lisa_btn_event(action),
};
LISA_LOGI(TAG, "[Button %d] action=%d", btn_id, action);
voice_msg_pub(VOICE_MSG_BUTTON_CHANGE, &evt, sizeof(evt));
}
static void evb_button_event_callback(lisa_btn_event_t event, uint8_t btn_id, void *user)
{
switch (event) {
case LISA_BTN_PRESS_DOWN:
publish_button_event(btn_id, LISA_BTN_PRESS_DOWN);
break;
case LISA_BTN_PRESS_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_CLICK);
break;
case LISA_BTN_PRESS_DOUBLE_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_DOUBLE_CLICK);
break;
case LISA_BTN_PRESS_TRIPLE_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_TRIPLE_CLICK);
break;
case LISA_BTN_PRESS_QUADRUPLE_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_QUADRUPLE_CLICK);
break;
case LISA_BTN_PRESS_QUINTUPLE_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_QUINTUPLE_CLICK);
break;
case LISA_BTN_PRESS_SEXTUPLE_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_SEXTUPLE_CLICK);
break;
case LISA_BTN_PRESS_SEPTUPLE_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_SEPTUPLE_CLICK);
break;
case LISA_BTN_PRESS_REPEAT_CLICK:
publish_button_event(btn_id, LISA_BTN_PRESS_REPEAT_CLICK);
break;
case LISA_BTN_PRESS_SHORT_START:
publish_button_event(btn_id, LISA_BTN_PRESS_SHORT_START);
break;
case LISA_BTN_PRESS_SHORT_UP:
publish_button_event(btn_id, LISA_BTN_PRESS_SHORT_UP);
break;
case LISA_BTN_PRESS_LONG_START:
publish_button_event(btn_id, LISA_BTN_PRESS_LONG_START);
break;
case LISA_BTN_PRESS_LONG_UP:
publish_button_event(btn_id, LISA_BTN_PRESS_LONG_UP);
break;
case LISA_BTN_PRESS_LONG_HOLD:
publish_button_event(btn_id, LISA_BTN_PRESS_LONG_HOLD);
break;
case LISA_BTN_PRESS_LONG_HOLD_UP:
publish_button_event(btn_id, LISA_BTN_PRESS_LONG_HOLD_UP);
break;
default:
break;
}
}
int service_button_init(void)
{
static const lisa_btn_gpio_item_t mini_power_key_gpio = {
.pin_num = POWER_KEY_PIN,
.iomux_func = CSK_AON_IOMUX_FUNC_DEFAULT,
.active_level = 0,
.pull_enable = false,
};
static const lisa_btn_gpio_config_t mini_power_key_cfg = {
.gpio_dev_name = "gpiob",
.button_count = 1,
.buttons = &mini_power_key_gpio,
.time_config =
{
.short_press_time = 1500,
.long_press_time = 3000,
.long_hold_time = 3000,
.scan_period = 20,
},
.callback = evb_button_event_callback,
.user_data = NULL,
};
int ret = lisa_btn_gpio_init(&mini_power_key_cfg);
if (ret != 0) {
LISA_LOGE(TAG, "Failed to init GPIO button: %d", ret);
return ret;
}
return 0;
}

View File

@@ -0,0 +1,8 @@
#ifndef SERVICE_BUTTON_H
#define SERVICE_BUTTON_H
#include <stdint.h>
int service_button_init(void);
#endif

View File

@@ -0,0 +1,240 @@
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "lisa_log.h"
#include "lisa_device.h"
#include "lisa_camera.h"
#include "lisa_gpio.h"
#include "IOMuxManager.h"
#include "service_camera.h"
#include "board.h"
#define TAG "service_camera"
#define CAMERA_DEVICE "camera"
#define DVP_DEVICE "dvp0"
#define I2C_DEVICE "i2c0"
#define DMA_CHANNEL 4
struct camera_context {
uint32_t inited: 1;
lisa_device_t *camera_dev;
lisa_device_t *i2c_dev;
lisa_device_t *dvp_dev;
lisa_device_t *gpioa;
lisa_device_t *gpiob;
lisa_camera_pixel_format_t pixel_format;
uint16_t width;
uint16_t height;
};
static struct camera_context cam_ctx = {
.inited = 0,
.camera_dev = NULL,
.i2c_dev = NULL,
.dvp_dev = NULL,
.gpioa = NULL,
.gpiob = NULL,
.pixel_format = LISA_CAMERA_PIXFMT_RGB565,
.width = 0,
.height = 0,
};
static void service_camera_context_reset(void)
{
cam_ctx.inited = 0;
cam_ctx.camera_dev = NULL;
cam_ctx.i2c_dev = NULL;
cam_ctx.dvp_dev = NULL;
cam_ctx.gpioa = NULL;
cam_ctx.gpiob = NULL;
cam_ctx.pixel_format = LISA_CAMERA_PIXFMT_RGB565;
cam_ctx.width = 0;
cam_ctx.height = 0;
}
int service_camera_init(void)
{
int ret;
if (cam_ctx.inited) {
LISA_LOGW(TAG, "Camera already initialized");
return 0;
}
service_camera_context_reset();
cam_ctx.camera_dev = lisa_device_get(CAMERA_DEVICE);
if (!cam_ctx.camera_dev || !lisa_device_ready(cam_ctx.camera_dev)) {
LISA_LOGE(TAG, "Camera device not ready");
return -1;
}
cam_ctx.i2c_dev = lisa_device_get(I2C_DEVICE);
if (!cam_ctx.i2c_dev || !lisa_device_ready(cam_ctx.i2c_dev)) {
LISA_LOGE(TAG, "I2C device not ready");
return -2;
}
cam_ctx.dvp_dev = lisa_device_get(DVP_DEVICE);
if (!cam_ctx.dvp_dev || !lisa_device_ready(cam_ctx.dvp_dev)) {
LISA_LOGE(TAG, "DVP device not ready");
return -3;
}
cam_ctx.gpioa = lisa_device_get("gpioa");
cam_ctx.gpiob = lisa_device_get("gpiob");
lisa_camera_config_t config = {
.hw_config =
{
.mclk_pad = CSK_IOMUX_PAD_A,
.mclk_pin = CAM_MCLK_PIN,
.xclk_delay_us = 0,
.i2c_dev = cam_ctx.i2c_dev,
},
.xclk_freq_hz = 18000000,
.fb_count = 3,
.enable_hmirror = false,
.enable_vflip = false,
.enable_colorbar = false,
};
ret = lisa_camera_setup(cam_ctx.camera_dev, &config);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(TAG, "Failed to setup camera: %d", ret);
return -4;
}
lisa_camera_bus_config_t bus_config = {
.dma_channel = DMA_CHANNEL,
.bus_type = LISA_CAMERA_BUS_DVP,
.config.dvp =
{
.dvp_dev = cam_ctx.dvp_dev,
.dvp_freq = config.xclk_freq_hz,
.data_align = 1,
.line_offset = 0,
.pixel_offset = 0,
.pclk_polarity = 0,
.vsync_polarity = 1,
.hsync_polarity = 1,
},
};
lisa_camera_crop_t crop = {
.x = 0,
.y = 0,
.width = 320,
.height = 240,
};
ret = lisa_camera_set_crop(cam_ctx.camera_dev, &crop);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(TAG, "Failed to set crop: %d", ret);
return -5;
}
int flip = 0;
if (lisa_kv_get_int("user.camera.flip", &flip) != 0) {
flip = 1;
}
if (flip) {
lisa_camera_set_hmirror(cam_ctx.camera_dev, true);
lisa_camera_set_vflip(cam_ctx.camera_dev, true);
} else {
lisa_camera_set_hmirror(cam_ctx.camera_dev, false);
lisa_camera_set_vflip(cam_ctx.camera_dev, false);
}
lisa_camera_get_framesize(cam_ctx.camera_dev, &bus_config.width, &bus_config.height);
bus_config.pixel_format = lisa_camera_get_pixformat(cam_ctx.camera_dev);
cam_ctx.width = bus_config.width;
cam_ctx.height = bus_config.height;
cam_ctx.pixel_format = bus_config.pixel_format;
ret = lisa_camera_attach_bus(cam_ctx.camera_dev, &bus_config);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(TAG, "Failed to attach bus: %d", ret);
return -6;
}
cam_ctx.inited = 1;
LISA_LOGI(TAG, "Camera initialized: %ux%u, format=%d", cam_ctx.width, cam_ctx.height, cam_ctx.pixel_format);
return 0;
}
int service_camera_capture(uint8_t *buffer, uint32_t buffer_len)
{
lisa_camera_fb_t *fb = NULL;
int ret;
if (!cam_ctx.inited || !cam_ctx.camera_dev) {
LISA_LOGE(TAG, "Camera not initialized");
return -1;
}
if (buffer == NULL || buffer_len < cam_ctx.width * cam_ctx.height * 2) {
LISA_LOGE(TAG, "Invalid parameters");
return -2;
}
ret = lisa_camera_start(cam_ctx.camera_dev);
if (ret != LISA_DEVICE_OK) {
LISA_LOGE(TAG, "Failed to start camera: %d", ret);
return -3;
}
ret = lisa_camera_capture(cam_ctx.camera_dev, &fb);
if (ret != LISA_DEVICE_OK || fb == NULL) {
LISA_LOGE(TAG, "Capture failed: %d", ret);
lisa_camera_stop(cam_ctx.camera_dev);
return -4;
}
if (buffer_len < fb->len) {
LISA_LOGE(TAG, "Buffer len %d is smaller than fb len %d", buffer_len, fb->len);
lisa_camera_release_fb(cam_ctx.camera_dev, fb);
lisa_camera_stop(cam_ctx.camera_dev);
return -5;
}
memcpy(buffer, fb->buf, fb->len);
lisa_camera_release_fb(cam_ctx.camera_dev, fb);
lisa_camera_stop(cam_ctx.camera_dev);
uint16_t *pixels = (uint16_t *)buffer;
for (uint32_t i = 0; i < cam_ctx.width * cam_ctx.height; i++) {
uint16_t pixel = pixels[i];
pixels[i] = (pixel >> 8) | (pixel << 8);
}
return 0;
}
int service_camera_get_framesize(uint16_t *width, uint16_t *height)
{
if (!cam_ctx.inited || !cam_ctx.camera_dev) {
LISA_LOGE(TAG, "Camera not initialized");
return -1;
}
if (!width || !height) {
LISA_LOGE(TAG, "Invalid parameters");
return -2;
}
*width = cam_ctx.width;
*height = cam_ctx.height;
return 0;
}
bool service_camera_is_inited(void)
{
return cam_ctx.inited;
}

View File

@@ -0,0 +1,12 @@
#ifndef SERVICE_CAMERA_H
#define SERVICE_CAMERA_H
#include <stdint.h>
#include <stdbool.h>
int service_camera_init(void);
int service_camera_capture(uint8_t *buffer, uint32_t buffer_len);
int service_camera_get_framesize(uint16_t *width, uint16_t *height);
bool service_camera_is_inited(void);
#endif

View File

@@ -0,0 +1,89 @@
#define TAG "service_image"
#include "service_image.h"
#include "lisa_mutex.h"
#include "lisa_log.h"
#include "voice_msg.h"
typedef struct {
bool inited;
enum {
SERVICE_IMAGE_STATE_INIT = 0,
SERVICE_IMAGE_STATE_WAITING,
SERVICE_IMAGE_STATE_CANCELED,
} state;
lisa_mutex_t *lock;
} service_image_ctx_t;
static service_image_ctx_t g_service_image = {0};
int service_image_init(void)
{
if (g_service_image.inited) {
return 0;
}
g_service_image.lock = lisa_mutex_create();
if (!g_service_image.lock) {
LOGE("create image service mutex failed");
return -1;
}
g_service_image.state = SERVICE_IMAGE_STATE_INIT;
g_service_image.inited = true;
return 0;
}
void service_image_waiting_start(void)
{
if (!g_service_image.inited || !g_service_image.lock) {
return;
}
lisa_mutex_lock(g_service_image.lock, LISA_WAIT_FOREVER);
g_service_image.state = SERVICE_IMAGE_STATE_WAITING;
lisa_mutex_unlock(g_service_image.lock);
LOGI("image waiting start");
}
void service_image_waiting_cancel(void)
{
if (!g_service_image.inited || !g_service_image.lock) {
return;
}
lisa_mutex_lock(g_service_image.lock, LISA_WAIT_FOREVER);
if (g_service_image.state != SERVICE_IMAGE_STATE_WAITING) {
lisa_mutex_unlock(g_service_image.lock);
return;
}
g_service_image.state = SERVICE_IMAGE_STATE_CANCELED;
lisa_mutex_unlock(g_service_image.lock);
voice_msg_pub(VOICE_MSG_CLOUD_MCP_LOADING, NULL, 0);
LOGI("image waiting cancel, drop next image");
}
bool service_image_waiting_get(void)
{
bool allowed = false;
if (!g_service_image.inited || !g_service_image.lock) {
return false;
}
lisa_mutex_lock(g_service_image.lock, LISA_WAIT_FOREVER);
if (g_service_image.state == SERVICE_IMAGE_STATE_WAITING) {
allowed = true;
g_service_image.state = SERVICE_IMAGE_STATE_INIT;
} else if (g_service_image.state == SERVICE_IMAGE_STATE_CANCELED) {
allowed = false;
g_service_image.state = SERVICE_IMAGE_STATE_INIT;
} else if (g_service_image.state == SERVICE_IMAGE_STATE_INIT) {
allowed = true;
}
lisa_mutex_unlock(g_service_image.lock);
return allowed;
}

View File

@@ -0,0 +1,25 @@
#ifndef __service_image_H__
#define __service_image_H__
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
int service_image_init(void);
/* 文生图进入等待态例如收到带qr标识的引导图URL */
void service_image_waiting_start(void);
/* 取消等待态(例如用户开始了新的交互) */
void service_image_waiting_cancel(void);
/* 等待态允许显示;取消态丢弃一次;初始化态默认允许显示一次 */
bool service_image_waiting_get(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,193 @@
#include "shell.h"
#include "stdint.h"
#include "stdbool.h"
#include "string.h"
#define TAG "led"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "lisa_log.h"
#include "lisa_gpio.h"
#include "lisa_thread.h"
#include "Driver_GPIO.h"
#include "IOMuxManager.h"
#include "board.h"
#define GPIO_DEVICE "gpiob"
typedef enum {
LED_CMD_ON = 0,
LED_CMD_OFF,
LED_CMD_BLINK,
LED_CMD_STOP,
} led_cmd_e;
typedef struct {
led_cmd_e cmd;
uint32_t on_ms;
uint32_t off_ms;
} led_msg_t;
static lisa_thread_t *s_led_task = NULL;
static QueueHandle_t s_led_queue = NULL;
static lisa_device_t *gpio_dev;
static int led_hw_init(void)
{
gpio_dev = lisa_device_get(GPIO_DEVICE);
if (!lisa_device_ready(gpio_dev)) {
LOGE("Error: %s device not ready", GPIO_DEVICE);
return -1;
}
int ret = lisa_gpio_configure(gpio_dev, LED_PIN, LISA_GPIO_OUTPUT | LISA_GPIO_OUTPUT_INIT_HIGH);
if (ret != 0) {
LOGE("Error: GPIO configuration failed (code: %d)", ret);
return -1;
}
return 0;
}
static void led_hw_on(void)
{
lisa_gpio_write_pin(gpio_dev, LED_PIN, LISA_GPIO_LOW);
}
static void led_hw_off(void)
{
lisa_gpio_write_pin(gpio_dev, LED_PIN, LISA_GPIO_HIGH);
}
static void led_task(void *param)
{
(void)param;
led_msg_t msg;
uint32_t on_ms = 0;
uint32_t off_ms = 0;
int blinking = 0;
int led_state = 0;
TickType_t last_wake_time = 0;
TickType_t next_toggle_time = 0;
led_hw_init();
last_wake_time = xTaskGetTickCount();
for (;;) {
TickType_t wait_time = portMAX_DELAY;
if (blinking) {
TickType_t current_time = xTaskGetTickCount();
if (current_time >= next_toggle_time) {
if (led_state) {
led_hw_off();
led_state = 0;
next_toggle_time = current_time + pdMS_TO_TICKS(off_ms);
} else {
led_hw_on();
led_state = 1;
next_toggle_time = current_time + pdMS_TO_TICKS(on_ms);
}
}
wait_time = next_toggle_time > current_time ? (next_toggle_time - current_time) : 0;
}
if (xQueueReceive(s_led_queue, &msg, wait_time) == pdPASS) {
switch (msg.cmd) {
case LED_CMD_ON:
blinking = 0;
led_state = 1;
led_hw_on();
break;
case LED_CMD_OFF:
blinking = 0;
led_state = 0;
led_hw_off();
break;
case LED_CMD_BLINK:
if (msg.on_ms == 0 && msg.off_ms == 0) {
blinking = 0;
led_state = 0;
led_hw_off();
} else {
on_ms = msg.on_ms ? msg.on_ms : 100;
off_ms = msg.off_ms ? msg.off_ms : 100;
blinking = 1;
led_state = 0;
next_toggle_time = xTaskGetTickCount();
}
break;
case LED_CMD_STOP:
default:
blinking = 0;
led_state = 0;
led_hw_off();
break;
}
}
}
}
void service_led_init(void)
{
if (s_led_queue != NULL) {
return;
}
s_led_queue = xQueueCreate(4, sizeof(led_msg_t));
if (s_led_queue == NULL) {
LISA_LOGE(TAG, "create queue failed");
return;
}
lisa_thread_attr_t attr = {
.name = (uint8_t *)"led_task",
.stack_size = 2048,
.priority = LISA_OS_PRIORITY_LOW,
};
s_led_task = lisa_thread_create(&attr, led_task, NULL);
if (s_led_task == NULL) {
LISA_LOGE(TAG, "create led task failed");
vQueueDelete(s_led_queue);
s_led_queue = NULL;
return;
}
LISA_LOGI(TAG, "LED service initialized");
}
static inline void led_send_cmd(led_cmd_e cmd, uint32_t on_ms, uint32_t off_ms)
{
if (!s_led_queue) {
LISA_LOGW(TAG, "LED queue not ready");
return;
}
led_msg_t msg = {.cmd = cmd, .on_ms = on_ms, .off_ms = off_ms};
BaseType_t ret = xQueueSend(s_led_queue, &msg, pdMS_TO_TICKS(100));
if (ret != pdPASS) {
LISA_LOGW(TAG, "LED queue send failed");
}
}
void service_led_on(void)
{
led_send_cmd(LED_CMD_ON, 0, 0);
}
void service_led_off(void)
{
led_send_cmd(LED_CMD_OFF, 0, 0);
}
void service_led_blink(uint32_t on_ms, uint32_t off_ms)
{
led_send_cmd(LED_CMD_BLINK, on_ms, off_ms);
}
void service_led_stop(void)
{
led_send_cmd(LED_CMD_STOP, 0, 0);
}

View File

@@ -0,0 +1,19 @@
#ifndef __LED_H__
#define __LED_H__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void service_led_init(void);
void service_led_on(void);
void service_led_off(void);
void service_led_blink(uint32_t on_ms, uint32_t off_ms);
#ifdef __cplusplus
}
#endif
#endif /* __LED_H__ */

View File

@@ -0,0 +1,57 @@
#include <stdbool.h>
#include <stdint.h>
#define TAG "volume"
#include "lisa_log.h"
#include "lisa_kv.h"
#include "kv.h"
#include "player_mgr.h"
#define DEFAULT_VOLUME 50
void service_volume_set(int volume);
void service_volume_init(void)
{
int volume = DEFAULT_VOLUME;
if (lisa_kv_get_int(KV_KEY_USER_VOLUME, &volume) == 0) {
service_volume_set(volume);
}
LOGI("Volume service initialized");
}
void service_volume_set(int volume)
{
if (volume < 0) {
volume = 0;
}
if (volume > 100) {
volume = 100;
}
lisa_kv_set_int(KV_KEY_USER_VOLUME, volume);
player_mgr_set_volume(volume);
LOGI("Volume set to %d", volume);
}
int service_volume_get(void)
{
int volume = DEFAULT_VOLUME;
if (lisa_kv_get_int(KV_KEY_USER_VOLUME, &volume) != 0) {
volume = DEFAULT_VOLUME;
}
return volume;
}
void service_volume_adjust(int delta)
{
int current_vol = service_volume_get();
int target_vol = current_vol + delta;
service_volume_set(target_vol);
}

View File

@@ -0,0 +1,11 @@
#ifndef SERVICE_VOLUME_H
#define SERVICE_VOLUME_H
#include <stdint.h>
void service_volume_init(void);
void service_volume_set(int volume);
int service_volume_get(void);
void service_volume_adjust(int delta);
#endif

View File

@@ -0,0 +1,9 @@
listenai_library_named(app_tone)
listenai_library_sources(
app_tone.c
)
listenai_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)

View File

@@ -0,0 +1,174 @@
#define TAG "tone"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "app_tone.h"
#include "lisa_log.h"
#include "lisa_mem.h"
#include "config_parser.h"
#include "romfs/romfs.h"
#include "tone.h"
static tone_hdr_t *s_tone_hdr = NULL;
static void _app_tone_uninit()
{
if (s_tone_hdr) {
if (s_tone_hdr->item) {
for (int i = 0; i < s_tone_hdr->total_cnt; i++) {
if (s_tone_hdr->item[i].url) {
lisa_mem_free(s_tone_hdr->item[i].url);
}
}
lisa_mem_free(s_tone_hdr->item);
}
lisa_mem_free(s_tone_hdr);
s_tone_hdr = NULL;
}
}
int app_tone_init(uint32_t flash_addr, uint32_t flash_size)
{
struct romfs *tone_fs = NULL;
struct romfs_dir_iter iter;
char path[ROMFS_PATH_MAX];
uint8_t *data = NULL;
uint32_t size = 0;
bool is_dir = false;
int tone_id = 0;
int tone_id_max = 0;
char tone_url[MAX_URL_LEN];
uint32_t total_mem_size = 0;
_app_tone_uninit();
if (flash_addr == 0 || flash_size == 0) {
LISA_LOGE(TAG, "Invalid tone ROMFS addr/size: addr=%p, size=%u", (void *)flash_addr, flash_size);
return -1;
}
s_tone_hdr = lisa_mem_calloc(1, sizeof(tone_hdr_t));
if (!s_tone_hdr) {
return -1;
}
total_mem_size += sizeof(tone_hdr_t);
// 初始化 ROMFS
if (romfs_init(&tone_fs, (const void *)flash_addr, flash_size) != 0) {
LISA_LOGE(TAG, "ROMFS init failed for tone addr=%p size=%u", (void *)flash_addr, flash_size);
goto TONE_INIT_FAIL;
}
LISA_LOGI(TAG, "Loading tone with ROMFS addr=%p size=%u", (void *)flash_addr, flash_size);
// 预扫描一遍,获取最大 tone_id
if (romfs_dir_iter_start(tone_fs, "/", &iter) != 0) {
LISA_LOGE(TAG, "ROMFS iterator start fail for tone");
goto TONE_INIT_FAIL;
}
while (romfs_dir_iter_next(&iter, path, sizeof(path), &data, &size, &is_dir) == 0) {
if (is_dir || data == NULL || size == 0 || path[0] != '/' || strlen(path) < 5) {
continue;
}
if (sscanf(path + 1, "%03d", &tone_id) != 1) {
continue;
}
if (tone_id > tone_id_max) {
tone_id_max = tone_id;
}
}
s_tone_hdr->total_cnt = tone_id_max + 1;
LISA_LOGI(TAG, "Found %d tones", s_tone_hdr->total_cnt);
// 分配 tone 映射表
total_mem_size += (s_tone_hdr->total_cnt * sizeof(tone_dsc_t));
s_tone_hdr->item = lisa_mem_calloc(s_tone_hdr->total_cnt, sizeof(tone_dsc_t));
if (!s_tone_hdr->item) {
LISA_LOGE(TAG, "Failed to alloc tone item table for %d items", s_tone_hdr->total_cnt);
goto TONE_INIT_FAIL;
}
// 从 ROMFS 创建音频索引
if (romfs_dir_iter_start(tone_fs, "/", &iter) != 0) {
LISA_LOGE(TAG, "ROMFS iterator start fail for tone");
goto TONE_INIT_FAIL;
}
while (romfs_dir_iter_next(&iter, path, sizeof(path), &data, &size, &is_dir) == 0) {
LISA_LOGD(TAG, "Found ROMFS file: %s, addr=%p size=%u", path, data, size);
if (is_dir || data == NULL || size == 0 || path[0] != '/' || strlen(path) < 5) {
continue;
}
if (sscanf(path + 1, "%03d", &tone_id) != 1) {
LISA_LOGI(TAG, "Ignore invalid tone file: %s", path);
continue;
}
if (tone_id >= s_tone_hdr->total_cnt) {
LISA_LOGW(TAG, "Tone id %d exceed max %d", tone_id, s_tone_hdr->total_cnt);
continue;
}
char tone_url[MAX_URL_LEN];
memset(tone_url, 0, MAX_URL_LEN);
snprintf(tone_url, MAX_URL_LEN, "mem://addr=%usize=%u", (uint32_t)data, size);
uint32_t url_len = strlen(tone_url) + 1;
s_tone_hdr->item[tone_id].tone_id = tone_id;
s_tone_hdr->item[tone_id].url = lisa_mem_alloc(url_len);
if (!s_tone_hdr->item[tone_id].url) {
LISA_LOGE(TAG, "Tone URL alloc fail for id %d", tone_id);
goto TONE_INIT_FAIL;
}
strcpy(s_tone_hdr->item[tone_id].url, tone_url);
total_mem_size += url_len;
LISA_LOGI(TAG, "Loaded tone[%d] from ROMFS: %s, size: %u", tone_id, path, size);
}
LISA_LOGI(TAG, "Loaded %d tones from ROMFS, total mem size: %u bytes", s_tone_hdr->total_cnt, total_mem_size);
return 0;
TONE_INIT_FAIL:
_app_tone_uninit();
return -1;
}
char *app_tone_get_url(uint16_t tone_id)
{
tone_dsc_t *item = NULL;
if (!s_tone_hdr || !s_tone_hdr->item) {
LISA_LOGE(TAG, "Tone not init or no item");
return NULL;
}
if (tone_id >= s_tone_hdr->total_cnt) {
LISA_LOGE(TAG, "Invalid tone id %d, exceed max %d", tone_id, s_tone_hdr->total_cnt);
return NULL;
}
item = &s_tone_hdr->item[tone_id];
if (item->url == NULL) {
LISA_LOGE(TAG, "Tone id %d url is null", tone_id);
return NULL;
}
return item->url;
}

View File

@@ -0,0 +1,35 @@
#ifndef __LISTENAI_APP_TONE_H__
#define __LISTENAI_APP_TONE_H__
#include <stdint.h>
#include <stdbool.h>
#define MAX_URL_LEN (32)
typedef struct {
uint16_t tone_id;
char *url;
} tone_dsc_t;
typedef struct {
uint16_t total_cnt;
tone_dsc_t *item;
} tone_hdr_t;
/**
* @brief 音频初始化
* @param flash_addr Flash起始地址
* @param flash_size Flash大小
* @return 0: 加载成功
* @return -1: 加载失败
*/
int app_tone_init(uint32_t flash_addr, uint32_t flash_size);
/**
* @brief 根据离线音频 ID 获取离线音频 URL
* @param tone_id 音频 ID
* @return 离线音频 URL
*/
char *app_tone_get_url(uint16_t tone_id);
#endif

112
apps/arcs-mini/tone/tone.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef __LISTENAI_TONE_PACKAGE_H__
#define __LISTENAI_TONE_PACKAGE_H__
enum {
TONE_ID_0 = 0,// 000_geeting.mp3
TONE_ID_1 = 1,// 001_ac_open.mp3
TONE_ID_2 = 2,// 002_ac_close.mp3
TONE_ID_3 = 3,// 003_screen_display_close.mp3
TONE_ID_4 = 4,// 004_clodmode.mp3
TONE_ID_5 = 5,// 005_heatmode.mp3
TONE_ID_6 = 6,// 006_windmode.mp3
TONE_ID_7 = 7,// 007_dehumidifymode.mp3
TONE_ID_8 = 8,// 008_cleaning_open.mp3
TONE_ID_9 = 9,// 009_cleaning_close.mp3
TONE_ID_10 = 10,// 010_sterilization_open.mp3
TONE_ID_11 = 11,// 011_sterilization_close.mp3
TONE_ID_12 = 12,// 012_low_wind.mp3
TONE_ID_13 = 13,// 013_medium_wind.mp3
TONE_ID_14 = 14,// 014_minimum_wind.mp3
TONE_ID_15 = 15,// 015_high_wind.mp3
TONE_ID_16 = 16,// 016_maximum_wind.mp3
TONE_ID_17 = 17,// 017_light_open.mp3
TONE_ID_18 = 18,// 018_light_close.mp3
TONE_ID_19 = 19,// 019_sleep_open.mp3
TONE_ID_20 = 20,// 020_sleep_close.mp3
TONE_ID_21 = 21,// 021_light_mode_open.mp3
TONE_ID_22 = 22,// 022_gohome_mode_open.mp3
TONE_ID_23 = 23,// 023_leavehome_mode_open.mp3
TONE_ID_24 = 24,// 024_allopen_mode_open.mp3
TONE_ID_25 = 25,// 025_allclose_mode_open.mp3
TONE_ID_26 = 26,// 026_read_mode_open.mp3
TONE_ID_27 = 27,// 027_video_mode_open.mp3
TONE_ID_28 = 28,// 028_sleep_mode_open.mp3
TONE_ID_29 = 29,// 029_rise_mode_open.mp3
TONE_ID_30 = 30,// 030_dining_mode_open.mp3
TONE_ID_31 = 31,// 031_20.mp3
TONE_ID_32 = 32,// 032_21.mp3
TONE_ID_33 = 33,// 033_22.mp3
TONE_ID_34 = 34,// 034_23.mp3
TONE_ID_35 = 35,// 035_24.mp3
TONE_ID_36 = 36,// 036_25.mp3
TONE_ID_37 = 37,// 037_curtain_open.mp3
TONE_ID_38 = 38,// 038_curtain_close.mp3
TONE_ID_39 = 39,// 039_balcony_lights_open.mp3
TONE_ID_40 = 40,// 040_balcony_lights_close.mp3
TONE_ID_41 = 41,// 041_shaking_open.mp3
TONE_ID_42 = 42,// 042_shaking_close.mp3
TONE_ID_43 = 43,// 043_tem_up.mp3
TONE_ID_44 = 44,// 044_tem_down.mp3
TONE_ID_45 = 45,// 045_up_down_swing_wind_open.mp3
TONE_ID_46 = 46,// 046_up_down_swing_wind_close.mp3
TONE_ID_47 = 47,// 047_left_right_swing_wind_open.mp3
TONE_ID_48 = 48,// 048_left_right_swing_wind_close.mp3
TONE_ID_49 = 49,// 049_swing_wind_open.mp3
TONE_ID_50 = 50,// 050_swing_wind_close.mp3
TONE_ID_51 = 51,// 051_max_vol.mp3
TONE_ID_52 = 52,// 052_min_vol.mp3
TONE_ID_53 = 53,// 053_increase_vol.mp3
TONE_ID_54 = 54,// 054_reduce_vol.mp3
TONE_ID_55 = 55,// 055_electric_heating_open.mp3
TONE_ID_56 = 56,// 056_electric_heating_close.mp3
TONE_ID_57 = 57,// 057_screen_display_open.mp3
TONE_ID_58 = 58,// 058_hello.mp3
TONE_ID_59 = 59,// 059_network_suc.mp3
TONE_ID_60 = 60,// 060_network_fail.mp3
TONE_ID_61 = 61,// 061_no_intention.mp3
TONE_ID_62 = 62,// 062_timeout.mp3
TONE_ID_63 = 63,// 063_geeting.pcm
TONE_ID_64 = 64,// 064_net_not_connected.mp3
TONE_ID_65 = 65,// 065_net_error.mp3
TONE_ID_66 = 66,// 066_enter_asr_oneshot.mp3
TONE_ID_67 = 67,// 067_enter_asr_continue.mp3
TONE_ID_68 = 68,// 068_enter_product_env.mp3
TONE_ID_69 = 69,// 069_enter_test_env.mp3
TONE_ID_70 = 70,// 070_enter_config_net_mode.mp3
TONE_ID_71 = 71,// 071_exit_config_net_mode.mp3
TONE_ID_72 = 72,// 072_config_net_success.mp3
TONE_ID_73 = 73,// 073_device_activation_failed.mp3
TONE_ID_74 = 74,// 074_config_net_timeout.mp3
TONE_ID_75 = 75,// 075_wifi_net_failed.mp3
TONE_ID_76 = 76,// 076_network_detecting_exited.mp3
TONE_ID_77 = 77,// 077_network_detecting_started.mp3
TONE_ID_78 = 78,// 078_wifi_connected.mp3
TONE_ID_79 = 79,// 079_wifi_no_connected.mp3
TONE_ID_80 = 80,// 080_network_can_not_connect.mp3
TONE_ID_81 = 81,// 081_network_detecting_ok.mp3
TONE_ID_82 = 82,// 082_network_connecting_ok.mp3
TONE_ID_83 = 83,// 083_device_token_failed.mp3
TONE_ID_84 = 84,// 084_device_token_ok.mp3
TONE_ID_85 = 85,// 085_cloud_connecting_faild.mp3
TONE_ID_86 = 86,// 086_start_recovery_factory.mp3
TONE_ID_87 = 87,// 087_new_version_exist.mp3
TONE_ID_88 = 88,// 088_alrady_the_latest_version.mp3
TONE_ID_89 = 89,// 089_new_version_download_failed.mp3
TONE_ID_90 = 90,// 090_new_version_download_successfully.mp3
TONE_ID_91 = 91,// 091_new_version_upgrading.mp3
TONE_ID_92 = 92,// 092_new_version_downloading.mp3
TONE_ID_93 = 93,// 093_upgrading.mp3
TONE_ID_94 = 94,// 094_alarm.mp3
TONE_ID_95 = 95,// 095_teeni.mp3
TONE_ID_96 = 96,// 096_manman.mp3
TONE_ID_97 = 97,// 097_boli.mp3
TONE_ID_98 = 98,// 098_niudun.mp3
TONE_ID_99 = 99,// 099_neza.mp3
TONE_ID_100 = 100,// 100_wukong.mp3
TONE_ID_101 = 101,// 101_peiji.mp3
TONE_ID_102 = 102,// 102_xiaoxian.mp3
TONE_ID_103 = 103,// 103_reset_factory.mp3
TONE_ID_104 = 104,// 104_device_config.mp3
TONE_ID_105 = 105,// 105_auth_failed.mp3
};
#endif

3
apps/arcs-mini/version Normal file
View File

@@ -0,0 +1,3 @@
major: 2
minor: 1
build: 0

View File

@@ -0,0 +1,40 @@
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/version VERSION_CONTENTS)
string(REGEX MATCH "major: *([0-9]+)" MAJOR_MATCH ${VERSION_CONTENTS})
set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "minor: *([0-9]+)" MINOR_MATCH ${VERSION_CONTENTS})
set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_1})
string(REGEX MATCH "build: *([0-9]+)" BUILD_MATCH ${VERSION_CONTENTS})
set(PROJECT_VERSION_BUILD ${CMAKE_MATCH_1})
set(PROJECT_VERSION_STR "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_BUILD}")
find_package(Git QUIET)
if(GIT_FOUND)
# Get short commit hash
execute_process(
COMMAND git rev-parse --short=8 HEAD
OUTPUT_VARIABLE PROJECT_VERSION_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_VARIABLE stderr
RESULT_VARIABLE return_code
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
if(return_code)
message(STATUS "git rev-parse failed: ${stderr}")
elseif(NOT "${stderr}" STREQUAL "")
message(STATUS "git rev-parse warned: ${stderr}")
endif()
endif()
if (NOT DEFINED PROJECT_VERSION_COMMIT)
set(PROJECT_VERSION_COMMIT "unknown")
endif()
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/version.h.in
${CMAKE_BINARY_DIR}/generated/include/project_version.h
@ONLY
)
include_directories(${CMAKE_BINARY_DIR}/generated/include)

View File

@@ -0,0 +1,7 @@
#pragma once
#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define PROJECT_VERSION_BUILD @PROJECT_VERSION_BUILD@
#define PROJECT_VERSION_COMMIT "@PROJECT_VERSION_COMMIT@"
#define PROJECT_VERSION_STR "@PROJECT_VERSION_STR@"

View File

@@ -0,0 +1,376 @@
#include <string.h>
#include <stdio.h>
#include "sys_init.h"
#include "voice_msg.h"
#include "app_datas.h"
#include "tone.h"
#include "app_tone.h"
#include "lisa_time.h"
#include "player_mgr.h"
#include "lisa_player.h"
#include "service_alarm.h"
#include "alarm_ring.h"
#define TAG "platform"
#include "lisa_log.h"
static bool s_disconnect_tone_played = false;
static bool s_content_hold_for_tts = false;
static char *voice_player_get_wakeup_tone_url(void)
{
static bool s_wakeup_tone_inited = false;
static uint16_t s_wakeup_tone_ids[5] = {0};
static uint8_t s_wakeup_tone_cnt = 0;
static bool s_last_wakeup_tone_valid = false;
static uint16_t s_last_wakeup_tone_id = TONE_ID_0;
if (!s_wakeup_tone_inited) {
for (uint16_t tone_id = TONE_ID_0; tone_id <= TONE_ID_4; tone_id++) {
if (app_tone_get_url(tone_id) != NULL) {
s_wakeup_tone_ids[s_wakeup_tone_cnt++] = tone_id;
}
}
if (s_wakeup_tone_cnt == 0) {
s_wakeup_tone_ids[s_wakeup_tone_cnt++] = TONE_ID_0;
}
s_wakeup_tone_inited = true;
LOGI("wakeup tone candidates count: %u", (unsigned int)s_wakeup_tone_cnt);
}
uint16_t tone_id = s_wakeup_tone_ids[0];
if (s_wakeup_tone_cnt > 1) {
do {
tone_id = s_wakeup_tone_ids[lisa_rand32() % s_wakeup_tone_cnt];
} while (s_last_wakeup_tone_valid && tone_id == s_last_wakeup_tone_id);
}
char *tone_url = app_tone_get_url(tone_id);
if (tone_url == NULL) {
tone_id = TONE_ID_0;
tone_url = app_tone_get_url(tone_id);
}
if (tone_url != NULL) {
s_last_wakeup_tone_id = tone_id;
s_last_wakeup_tone_valid = true;
}
return tone_url;
}
static void voice_player_play_wakeup_tone(void)
{
char *tone_url = voice_player_get_wakeup_tone_url();
if (tone_url == NULL) {
LOGE("wakeup tone url is null");
return;
}
player_mgr_play(AIP, tone_url, 0);
}
static void hold_content_until_tts_playing(void)
{
if (!s_content_hold_for_tts) {
return;
}
if (!player_mgr_is_playing(TTS) && player_mgr_is_playing(CONTENT)) {
LOGI("re-pause content while waiting tts start");
player_mgr_pause_temporary(CONTENT);
}
}
void voice_player_play_msg(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
struct app_datas *app_datas = get_app_datas();
if (app_datas == NULL) {
LOGW("Invalid app_datas");
return;
}
switch (msg_id) {
case VOICE_MSG_WAKEUP_BUTTON_START: {
if ((app_datas->voice_work_mode & VOICE_WORK_MODE_BUTTON_WAKEUP) == 0) {
return;
}
if (!app_datas->can_wakeup) {
return;
}
if (app_datas->voice_cloud_connected == 1) {
voice_player_play_wakeup_tone();
} else if (!app_datas->wifi_connected) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_64), 0);
} else if (app_datas->auth_failed) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_105), 0);
} else {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_85), 0);
}
} break;
case VOICE_MSG_WAKEUP_KEYWORD: {
if ((app_datas->voice_work_mode & VOICE_WORK_MODE_VOICE_WAKEUP) == 0) {
return;
}
if (!app_datas->can_wakeup) {
return;
}
if (app_datas->voice_cloud_connected == 1) {
voice_player_play_wakeup_tone();
} else if (!app_datas->wifi_connected) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_64), 0);
} else if (app_datas->auth_failed) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_105), 0);
} else {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_85), 0);
}
} break;
case VOICE_MSG_CLOUD_CLOUD_AUTH_SUCCESS: {
app_datas->auth_failed = 0;
s_disconnect_tone_played = false;
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_59), 0);
} break;
case VOICE_MSG_CLOUD_CLOUD_AUTH_FAILED: {
app_datas->auth_failed = 1;
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_105), 0);
} break;
case VOICE_MSG_CLOUD_DISCONNECTED: {
if (!s_disconnect_tone_played) {
if(app_datas->wifi_connected) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_65), 0);
} else {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_60), 0);
}
s_disconnect_tone_played = true;
}
} break;
case VOICE_MSG_WIFI_CONNECTED:{
s_disconnect_tone_played = false;
} break;
case VOICE_MSG_WIFI_DISCONNECTED: {
if (!s_disconnect_tone_played) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_60), 0);
s_disconnect_tone_played = true;
}
} break;
case VOICE_MSG_CLOUD_TTS_URL: {
if (data == NULL) {
LOGE("Invalid data");
break;
}
LOGI("Ready to play tts url: %s", (char *)data);
player_mgr_play(TTS, (char *)data, 0);
} break;
case VOICE_MSG_CLOUD_IAT_UPDATE: {
if (data == NULL || len == 0 || ((char *)data)[0] == '\0') {
break;
}
bool tts_playing = player_mgr_is_playing(TTS);
bool content_playing = player_mgr_is_playing(CONTENT);
if (!tts_playing && !content_playing) {
break;
}
s_content_hold_for_tts = true;
if (tts_playing) {
LOGI("stop tts due to valid iat update");
player_mgr_stop(TTS);
}
if (content_playing) {
LOGI("pause content due to valid iat update");
if (player_mgr_pause_temporary(CONTENT) == 0) {
}
}
} break;
case VOICE_MSG_CLOUD_SESSION_FINISHED: {
s_content_hold_for_tts = false;
} break;
default:
break;
}
}
static void voice_player_audio_item(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
struct voice_msg_audio_items *items = (struct voice_msg_audio_items *)data;
if (items == NULL) {
LOGE("Invalid data");
return;
}
if (items->cnt == 0) {
LOGE("Invalid cnt");
return;
}
audio_out_t *list = (audio_out_t *)lisa_mem_calloc(items->cnt, sizeof(audio_out_t));
if (list == NULL) {
LOGE("Failed to allocate memory");
return;
}
for (int i = 0; i < items->cnt; i++) {
memcpy(list[i].mid, items->items[i].id, strlen(items->items[i].id) + 1);
}
player_mgr_play_array(CONTENT, list, items->cnt);
lisa_mem_free(list);
}
static void voice_player_play_control(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
switch (msg_id) {
case VOICE_MSG_PLAY_CONTROL_PLAY: {
player_mgr_resume(CONTENT);
} break;
case VOICE_MSG_PLAY_CONTROL_PAUSE: {
player_mgr_pause(CONTENT);
} break;
case VOICE_MSG_PLAY_CONTROL_NEXT: {
player_mgr_play_next(CONTENT);
} break;
case VOICE_MSG_PLAY_CONTROL_PREVIOUS: {
player_mgr_play_prev(CONTENT);
} break;
case VOICE_MSG_PLAY_CONTROL_REPLAY: {
} break;
default:
break;
}
}
static void on_tts_play_status(int player_id, uint16_t status, void *arg)
{
uint32_t msg = 0;
LOGI("player id: %d, status: %d", player_id, status);
if (status == PLAYER_EVT_PLAYING) {
s_content_hold_for_tts = false;
voice_msg_pub(VOICE_MSG_PLAYER_TTS_PLAYING, NULL, 0);
} else if (status == PLAYER_EVT_PAUSED) {
voice_msg_pub(VOICE_MSG_PLAYER_TTS_PAUSED, NULL, 0);
} else if (status == PLAYER_EVT_PLAYBACK_COMPLETE) {
voice_msg_pub(VOICE_MSG_PLAYER_TTS_STOPED, NULL, 0);
if (alarm_ring_is_active()) {
alarm_ring_notify_playback_complete();
}
} else if (status == PLAYER_EVT_STOPED || status == PLAYER_EVT_ERROR) {
voice_msg_pub(VOICE_MSG_PLAYER_TTS_STOPED, NULL, 0);
}
}
static void on_content_focus_status(int player_id, focus_state_e state, int by_which, void *arg)
{
(void)player_id;
(void)by_which;
(void)arg;
if (state == FOREGROUND) {
hold_content_until_tts_playing();
}
}
static void on_local_play_status(int player_id, uint16_t status, void *arg)
{
(void)player_id;
(void)arg;
if (status == PLAYER_EVT_PLAYBACK_COMPLETE && alarm_ring_is_active()) {
alarm_ring_notify_playback_complete();
}
}
static void voice_alarm_ring_play_once(const char *text)
{
struct app_datas *app_datas = get_app_datas();
if (app_datas == NULL || app_datas->voice_cloud_connected == 0 || !text || text[0] == 0) {
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_94), 0);
return;
}
char *tts_text = lisa_mem_alloc(256);
if (tts_text == NULL) {
LOGW("voice_alarm_ring_play_once, alloc failed, fallback local tone");
player_mgr_play(LOCAL, app_tone_get_url(TONE_ID_94), 0);
return;
}
snprintf(tts_text, 256, "你有一个 %s 的提醒, 请不要忘记哦!", text);
voice_cloud_tts_synth(tts_text);
lisa_mem_free(tts_text);
}
static void voice_alarm_ring_force_stop(void)
{
player_mgr_stop(LOCAL);
player_mgr_stop(TTS);
}
static void voice_player_mcp_chat_exit(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LOGI("voice_player_mcp_chat_exit, stop content player");
s_content_hold_for_tts = false;
audio_player_stop_by_user();
}
void voice_player_ready(void *unused, uint32_t msg_id, void *data, uint32_t len, void *user_data)
{
LISA_LOGI(TAG, "voice_player_ready");
#ifdef CONFIG_VOICE_LINK_LSCHAT
voice_msg_sub(VOICE_MSG_WAKEUP_BUTTON_START, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_WAKEUP_KEYWORD, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_CLOUD_AUTH_SUCCESS, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_CLOUD_AUTH_FAILED, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_DISCONNECTED, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_TTS_URL, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_IAT_UPDATE, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_SESSION_FINISHED, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_AUDIO_ITEM, voice_player_audio_item, NULL);
voice_msg_sub(VOICE_MSG_CLOUD_MCP_CHAT_EXIT, voice_player_mcp_chat_exit, NULL);
#else
LISA_LOGI(TAG, "LSChat link inactive, skip cloud player subscriptions");
#endif
voice_msg_sub(VOICE_MSG_WIFI_DISCONNECTED, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_WIFI_CONNECTED, voice_player_play_msg, NULL);
voice_msg_sub(VOICE_MSG_PLAY_CONTROL_PLAY, voice_player_play_control, NULL);
voice_msg_sub(VOICE_MSG_PLAY_CONTROL_PAUSE, voice_player_play_control, NULL);
voice_msg_sub(VOICE_MSG_PLAY_CONTROL_NEXT, voice_player_play_control, NULL);
voice_msg_sub(VOICE_MSG_PLAY_CONTROL_PREVIOUS, voice_player_play_control, NULL);
voice_msg_sub(VOICE_MSG_PLAY_CONTROL_REPLAY, voice_player_play_control, NULL);
alarm_ring_init(voice_alarm_ring_play_once, voice_alarm_ring_force_stop);
player_mgr_register_status_cb(TTS, on_tts_play_status, NULL);
player_mgr_register_status_cb(LOCAL, on_local_play_status, NULL);
player_mgr_register_focus_cb(CONTENT, on_content_focus_status, NULL);
}
static int voice_player_init(void)
{
LISA_LOGI(TAG, "voice_player_init");
voice_msg_sub(VOICE_MSG_PLATFORM_READY, voice_player_ready, NULL);
return 0;
}
SYS_INIT(voice_player_init, SYS_INIT_LEVEL_PRE_APPLICATION, 50);