模式模块负责管理多种 AI 对话 触发方式 (长按、单次按键、关键词唤醒、P2P、翻译等),为实现各种交互模式提供灵活的框架。各模式均基于同一套 状态机 实现,通过实现一组可选回调即可扩展新模式。
mode/
├── wukong_ai_mode.h/c # 模式管理器,提供对外接口
├── wukong_ai_mode_free.c # 自由对话模式
├── wukong_ai_mode_hold.c # 长按对话模式
├── wukong_ai_mode_oneshot.c # 单次按键回合对话模式
├── wukong_ai_mode_wakeup.c # 关键词唤醒回合模式
├── wukong_ai_mode_p2p.c # P2P 通信模式(需 App 面板触发)
└── wukong_ai_mode_translate.c # 翻译模式
| 缩写 | 英文全称 | 中文含义 |
|---|---|---|
| AEC | Acoustic Echo Cancellation | 回声消除 |
| VAD | Voice Activity Detection | 语音端点检测 |
| KWS | Keyword Spotting | 关键词唤醒 |
| ASR | Automatic Speech Recognition | 自动语音识别 |
| TTS | Text-to-Speech | 文本转语音 |
| P2P | Peer-to-Peer | 点对点(设备与 App 直连) |
所有模式均默认支持 AEC。差异主要体现在 触发方式、VAD 模式、是否启用 KWS/服务器 VAD 以及 典型场景。
| 模式名称 | 触发方式 | 使用场景 | 特性 |
|---|---|---|---|
| 长按对话模式 ( wukong_ai_mode_hold) |
长按按键 | 按键通话式对话 |
|
| 单次按键回合对话模式 ( wukong_ai_mode_oneshot) |
单次按键 | 回合制对话 |
|
| 关键词唤醒回合模式 ( wukong_ai_mode_wakeup) |
关键词唤醒 | 免提语音交互 |
|
| 自由对话模式 ( wukong_ai_mode_free) |
关键词唤醒 + 连续对话 | 自然对话流程 |
|
| P2P 模式 ( wukong_ai_mode_p2p) |
P2P 通信,需 App 面板触发 | 直接点对点音频通信 |
|
| 翻译模式 ( wukong_ai_mode_translate) |
关键词唤醒 | 实时语言翻译 |
|
模式的启用、板型与功能开关均在 应用配置 中完成。
make app_menuconfig APP_NAME=tuyaos_demo_wukong_aiAPPConfig 等),修改后需执行 make app_config 生成头文件。所有模式共用同一状态机,状态流转如下:
INIT → IDLE → LISTEN → UPLOAD → THINK → SPEAK
↑ ↑ ↓
└───────└─────────────────────────┘
| 状态 | 含义 |
|---|---|
| INIT | 初始化 |
| IDLE | 空闲,等待触发 |
| LISTEN | 监听用户输入 |
| UPLOAD | 上传音频到云端 |
| THINK | 云端 AI 处理 |
| SPEAK | 播放 TTS 回复 |
/**
* @brief 初始化模式系统
*
* 注册所有启用的模式并初始化默认模式。
*
* @return 成功返回 OPRT_OK,否则返回错误码
*/
OPERATE_RET wukong_ai_mode_init(VOID);
/**
* @brief 切换到下一个启用的模式
*
* @param[in] cur_mode 当前模式
* @return 成功返回 OPRT_OK,否则返回错误码
*/
OPERATE_RET wukong_ai_mode_switch(AI_TRIGGER_MODE_E cur_mode);
/**
* @brief 切换到指定模式
*
* @param[in] mode 目标模式
* @return 成功返回 OPRT_OK,否则返回错误码
*/
OPERATE_RET wukong_ai_mode_switch_to(AI_TRIGGER_MODE_E mode);
提供统一的接口,处理各种状态、消息。对外只暴露单一入口 wukong_ai_mode_dispatch,通过操作枚举将请求分发给当前模式的对应回调。
/** 操作类型:与 AI_CHAT_MODE_HANDLE_T 中的回调一一对应 */
typedef enum {
AI_MODE_OP_INIT, AI_MODE_OP_DEINIT, AI_MODE_OP_KEY, AI_MODE_OP_TASK,
AI_MODE_OP_EVENT, AI_MODE_OP_WAKEUP, AI_MODE_OP_VAD, AI_MODE_OP_CLIENT,
AI_MODE_OP_NOTIFY_IDLE, AI_MODE_OP_AUDIO_INPUT, AI_MODE_OP_MAX,
} AI_MODE_OP_E;
/**
* @brief 将操作分发给当前模式(推荐使用)
* @param[in] op 操作类型
* @param[in] data 载荷(可为 NULL)
* @param[in] len 载荷长度
* @return OPRT_OK 成功,OPRT_NOT_FOUND 无对应处理,或回调返回值
*/
OPERATE_RET wukong_ai_mode_dispatch(AI_MODE_OP_E op, VOID *data, INT_T len);
每个模式按需实现以下接口,未实现的接口(赋值为 NULL)在事件分发时会被安全拦截或采用默认行为。
typedef struct {
OPERATE_RET (*on_init)(VOID *data, INT_T len); /* 模式进入时的初始化(如设置VAD模式、启停KWS等) */
OPERATE_RET (*on_deinit)(VOID *data, INT_T len); /* 模式退出时的反初始化(资源清理) */
OPERATE_RET (*on_key)(VOID *data, INT_T len); /* 物理按键事件触发(如长按/短按) */
OPERATE_RET (*on_task)(VOID *data, INT_T len); /* 定时任务或主循环轮询(处理超时或延迟操作) */
OPERATE_RET (*on_event)(VOID *data, INT_T len); /* AI 云端或系统事件(如 ASR 识别结果、TTS 播放状态等) */
OPERATE_RET (*on_wakeup)(VOID *data, INT_T len); /* 语音关键词本地唤醒事件 */
OPERATE_RET (*on_vad)(VOID *data, INT_T len); /* 语音端点检测事件(检测到人声起点/尾点) */
OPERATE_RET (*on_client)(VOID *data, INT_T len); /* 客户端业务指令或其他网络下发数据 */
OPERATE_RET (*on_notify_idle)(VOID *data, INT_T len); /* 系统空闲状态通知(可用于超时自动切回默认状态) */
OPERATE_RET (*on_audio_input)(VOID *data, INT_T len); /* 音频数据流输入(如果不实现该回调,将走默认云端上行) */
} AI_CHAT_MODE_HANDLE_T;
各模式内部可共用 AI_CHAT_MODE_PARAM_T(在 wukong_ai_mode.h 中定义,含 wakeup_stat、 state )作为上下文结构。
在模式内部做状态切换并打印日志时使用。
#define MODE_STATE_CHANGE(_mode, _old, _new) \
do { \
PR_DEBUG("模式 %s 状态从 %s 变更为 %s", \
_mode_str[_mode], _state_str[_old], _state_str[_new]); \
_old = _new; \
} while (0)
#include "wukong_ai_mode.h"
/* 初始化模式系统 */
OPERATE_RET init_modes(VOID)
{
OPERATE_RET rt = OPRT_OK;
/* 初始化模式管理器 */
/* 这将注册所有启用的模式并启动默认模式 */
rt = wukong_ai_mode_init();
if (rt != OPRT_OK) {
printf("初始化模式失败: %d\n", rt);
return rt;
}
return rt;
}
/* 切换到唤醒模式 */
wukong_ai_mode_switch_to(AI_TRIGGER_MODE_WAKEUP);
/* 切换到下一个启用的模式 */
AI_TRIGGER_MODE_E current = tuya_ai_toy_trigger_mode_get();
wukong_ai_mode_switch(current);
/* 按键 */
PUSH_KEY_TYPE_E key_event = NORMAL_KEY;
wukong_ai_mode_dispatch(AI_MODE_OP_KEY, &key_event, sizeof(key_event));
/* AI 事件 */
WUKONG_AI_EVENT_T event = { .type = WUKONG_AI_EVENT_ASR_OK, .data = asr_result };
wukong_ai_mode_dispatch(AI_MODE_OP_EVENT, &event, 0);
/* 唤醒、VAD */
wukong_ai_mode_dispatch(AI_MODE_OP_WAKEUP, NULL, 0);
WUKONG_AUDIO_VAD_FLAG_E vad_flag = WUKONG_AUDIO_VAD_START;
wukong_ai_mode_dispatch(AI_MODE_OP_VAD, &vad_flag, sizeof(vad_flag));
/* 在模式内部使用 MODE_STATE_CHANGE 进行状态转换 */
MODE_STATE_CHANGE(AI_TRIGGER_MODE_WAKEUP, s_ai_wakeup.state, AI_CHAT_LISTEN);
在 mode/ 下新增 wukong_ai_mode_my_mode.c ,仅依赖 wukong_ai_mode.h ,无需单独头文件。
ENABLE_AI_MODE_MY_MODE (或当前模式名)选项,使 make app_menuconfig 中可勾选。config 下添加 select ENABLE_AI_MODE_MY_MODE;若希望所有板型可选,则为各板型分别添加。make app_config APP_NAME=tuyaos_demo_wukong_ai 生成头文件。/* wukong_ai_mode_my_mode.c */
#include "wukong_ai_mode.h"
#if defined(ENABLE_AI_MODE_MY_MODE) && (ENABLE_AI_MODE_MY_MODE == 1)
STATIC AI_CHAT_MODE_HANDLE_T s_ai_my_mode_cb = {0};
STATIC AI_CHAT_MODE_PARAM_T s_ai_my_mode = {0};
STATIC AI_CHAT_STATE_E s_ai_cur_state = AI_CHAT_INVALID;
/* 状态回调 */
STATIC OPERATE_RET __ai_my_mode_idle_cb(VOID *data, INT_T len)
{
tuya_ai_toy_led_off();
wukong_audio_input_wakeup_set(FALSE);
return OPRT_OK;
}
STATIC OPERATE_RET __ai_my_mode_listen_cb(VOID *data, INT_T len)
{
tuya_ai_toy_led_flash(500);
wukong_audio_input_wakeup_set(TRUE);
return OPRT_OK;
}
/* ... 实现其他状态回调 ... */
/* 事件处理器 */
STATIC OPERATE_RET wukong_ai_my_mode_event_cb(VOID *data, INT_T len)
{
WUKONG_AI_EVENT_T *event = (WUKONG_AI_EVENT_T *)data;
switch (event->type) {
case WUKONG_AI_EVENT_ASR_OK:
MODE_STATE_CHANGE(AI_TRIGGER_MODE_MY_MODE, s_ai_my_mode.state, AI_CHAT_THINK);
break;
case WUKONG_AI_EVENT_TTS_PRE:
MODE_STATE_CHANGE(AI_TRIGGER_MODE_MY_MODE, s_ai_my_mode.state, AI_CHAT_SPEAK);
break;
/* ... 处理其他事件 ... */
}
return OPRT_OK;
}
/* 初始化处理器 */
STATIC OPERATE_RET wukong_ai_my_mode_init_cb(VOID *data, INT_T len)
{
wukong_audio_input_wakeup_mode_set(WUKONG_AUDIO_VAD_AUTO);
wukong_kws_enable();
MODE_STATE_CHANGE(AI_TRIGGER_MODE_MY_MODE, s_ai_my_mode.state, AI_CHAT_IDLE);
return OPRT_OK;
}
/* 注册模式 */
OPERATE_RET ai_my_mode_register(AI_CHAT_MODE_HANDLE_T **cb)
{
s_ai_my_mode_cb.on_init = wukong_ai_my_mode_init_cb;
s_ai_my_mode_cb.on_deinit = wukong_ai_my_mode_deinit_cb;
s_ai_my_mode_cb.on_key = wukong_ai_my_mode_key_cb;
s_ai_my_mode_cb.on_task = wukong_ai_my_mode_task_cb;
s_ai_my_mode_cb.on_event = wukong_ai_my_mode_event_cb;
s_ai_my_mode_cb.on_wakeup = wukong_ai_my_mode_wakeup;
s_ai_my_mode_cb.on_vad = wukong_ai_my_mode_vad;
s_ai_my_mode_cb.on_client = wukong_ai_my_mode_client_run;
s_ai_my_mode_cb.on_notify_idle = wukong_ai_my_mode_notify_idle_cb;
*cb = &s_ai_my_mode_cb;
return OPRT_OK;
}
#endif /* ENABLE_AI_MODE_MY_MODE */
在 wukong_ai_mode.c 中增加对 ai_my_mode_register 的 extern 声明,并在 wukong_ai_mode_init() 里按现有模式方式注册(根据 ENABLE_AI_MODE_MY_MODE 开关启用,并设置 s_ai_mode_map[AI_TRIGGER_MODE_MY_MODE])。
不修改配置宏的前提下,需在枚举中新增 AI_TRIGGER_MODE_MY_MODE 并在 _mode_str 等表中补齐。
在 wukong_ai_mode.h 的 AI_TRIGGER_MODE_E 中增加新枚举值(如 AI_TRIGGER_MODE_MY_MODE),并同步更新 wukong_ai_mode.c 中的 _mode_str 等表。
配置宏(如 ENABLE_AI_MODE_*)由现有构建/配置体系管理,此处不新增或修改配置宏定义。
开发过程中如有问题,可到 TuyaOS 开发者论坛 > 联网单品开发版块 发帖咨询。
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈