Skip to main content

Voice Chat Modes

A voice chat mode decides when the device listens, uploads, and replies — press-and-hold to talk, click once, wake by keyword, or chat hands-free. ai_manage_mode is the component that registers these modes, switches between them, and routes events (user, VAD, key) to whichever mode is active.

It sits between the device's inputs (button, microphone, wake word) and ai_agent, which does the actual cloud talking. A mode never uploads audio itself; it decides the moment to start and stop, then drives ai_agent.

Terms

TermMeaning
Chat modeThe interaction style that decides when the device listens and uploads — hold, oneshot, wakeup, or free.
VADVoice Activity Detection — detects whether the user is currently speaking.
Mode handleAn AI_MODE_HANDLE_T — the set of callbacks one mode implements (init, task, event handling, and so on).

What a chat mode is

Every mode answers one question: when should the device start and stop capturing the user's voice? The four built-in modes answer it differently.

ModeEnumTriggerStops capturing on
HoldAI_CHAT_MODE_HOLDPress and hold the buttonButton release
One-shotAI_CHAT_MODE_ONE_SHOTClick the button onceVAD detects end of speech
Wake wordAI_CHAT_MODE_WAKEUPSpeak the wake wordVAD detects end of speech
FreeAI_CHAT_MODE_FREEAlways listeningNever — continuous

The enum is AI_CHAT_MODE_E. Custom modes start at AI_CHAT_MODE_CUSTOM_START (0x100) so their values never collide with the built-in ones.

typedef enum {
AI_CHAT_MODE_HOLD,
AI_CHAT_MODE_ONE_SHOT,
AI_CHAT_MODE_WAKEUP,
AI_CHAT_MODE_FREE,

AI_CHAT_MODE_CUSTOM_START = 0x100,
} AI_CHAT_MODE_E;

The mode lifecycle

Whatever the trigger, every mode runs the same state machine, exposed as AI_MODE_STATE_E. The active mode advances through these states as a turn progresses; query the current one with ai_mode_get_state.

StateMeaning
AI_MODE_STATE_INITThe mode is being initialized.
AI_MODE_STATE_IDLEInitialized and waiting for a trigger.
AI_MODE_STATE_LISTENCapturing the user's voice.
AI_MODE_STATE_UPLOADSending the captured audio to the cloud.
AI_MODE_STATE_THINKThe cloud is processing (ASR + reasoning).
AI_MODE_STATE_SPEAKPlaying back the cloud's reply.
AI_MODE_STATE_INVALIDNo mode is active, or the mode is uninitialized.
note

ai_mode_get_state returns AI_MODE_STATE_INVALID when no mode has been initialized. Initialize a mode with ai_mode_init before relying on the state.

How a mode is implemented

A mode is a set of callbacks gathered in an AI_MODE_HANDLE_T, registered against an AI_CHAT_MODE_E value with ai_mode_register. Only name, init, deinit, task, handle_event, get_state, and client_run are required; vad_change and handle_key exist only when the audio and button components are enabled.

typedef struct {
const char *name;

OPERATE_RET (*init) (void);
OPERATE_RET (*deinit) (void);
OPERATE_RET (*task) (void *args);
OPERATE_RET (*handle_event) (AI_NOTIFY_EVENT_T *event);
AI_MODE_STATE_E (*get_state) (void);
OPERATE_RET (*client_run) (void *data);

#if defined(ENABLE_COMP_AI_AUDIO) && (ENABLE_COMP_AI_AUDIO == 1)
OPERATE_RET (*vad_change) (AI_AUDIO_VAD_STATE_E vad_state);
#endif

#if defined(ENABLE_BUTTON) && (ENABLE_BUTTON == 1)
OPERATE_RET (*handle_key) (TDL_BUTTON_TOUCH_EVENT_E event, void *arg);
#endif
} AI_MODE_HANDLE_T;

The built-in modes already provide their handles; you register them with one call each (ai_mode_hold_register, ai_mode_oneshot_register, and the rest). You only define your own AI_MODE_HANDLE_T when you build a custom mode.

API reference

Header: ai_manage_mode.h. Functions return OPERATE_RET (OPRT_OK on success) unless noted otherwise.

FunctionParametersPurpose
ai_mode_registermode, handleRegister a mode handle against a chat-mode value. Registration order sets the ai_mode_switch_next cycle order.
ai_mode_initmodeInitialize a registered mode and make it the active mode.
ai_mode_deinitDeinitialize the active mode.
ai_mode_task_runningargsRun the active mode's task callback — call this in your loop to advance its state machine.
ai_mode_handle_eventeventForward an AI_NOTIFY_EVENT_T to the active mode.
ai_mode_get_stateReturn the active mode's AI_MODE_STATE_E (AI_MODE_STATE_INVALID if none).
ai_mode_client_rundataRun the active mode's client_run callback.
ai_mode_vad_changevad_stateForward a VAD state change to the active mode. Requires ENABLE_COMP_AI_AUDIO.
ai_mode_handle_keyevent, argForward a button event to the active mode. Requires ENABLE_BUTTON.
ai_mode_get_curr_modemode (out)Get the active chat mode.
ai_mode_switchmodeSwitch to another mode — deinitializes the current one and initializes the target.
ai_mode_switch_nextSwitch to the next registered mode and return its AI_CHAT_MODE_E value.
ai_get_mode_state_strstateReturn a human-readable name for a state.
ai_get_mode_name_strmodeReturn a human-readable name for a mode.
ai_mode_is_in_register_listmodeReturn TRUE if the mode is registered.
ai_get_first_modeout_mode (out)Get the first registered mode.
tip

ai_mode_switch_next cycles modes in the order you registered them. Wire it to a long-press or a settings toggle to let the user rotate through chat modes at runtime.

Wire it into an app

Register the modes you need at startup, initialize a default one, then run the task loop and forward events.

#include "ai_manage_mode.h"
#include "ai_mode_hold.h"
#include "ai_mode_oneshot.h"

OPERATE_RET ai_modes_start(void)
{
OPERATE_RET rt = OPRT_OK;

// 1. Register the modes you want. Registration order = switch-next order.
TUYA_CALL_ERR_RETURN(ai_mode_hold_register());
TUYA_CALL_ERR_RETURN(ai_mode_oneshot_register());

// 2. Initialize a default mode.
TUYA_CALL_ERR_RETURN(ai_mode_init(AI_CHAT_MODE_HOLD));
return rt;
}

// 3. Advance the active mode's state machine in your loop.
void ai_mode_loop(void *args)
{
while (1) {
ai_mode_task_running(args);
tal_system_sleep(10);
}
}

// Rotate to the next registered mode (e.g. from a long-press).
void ai_mode_cycle(void)
{
AI_CHAT_MODE_E next = ai_mode_switch_next();
PR_NOTICE("Switched to mode: %s", ai_get_mode_name_str(next));
}

Add a custom mode

Implement the required callbacks, fill an AI_MODE_HANDLE_T, and register it with a value at or above AI_CHAT_MODE_CUSTOM_START.

static AI_MODE_STATE_E sg_state = AI_MODE_STATE_IDLE;

static OPERATE_RET my_mode_init(void) { sg_state = AI_MODE_STATE_IDLE; return OPRT_OK; }
static OPERATE_RET my_mode_deinit(void) { return OPRT_OK; }
static AI_MODE_STATE_E my_mode_get_state(void) { return sg_state; }

static OPERATE_RET my_mode_task(void *args)
{
switch (sg_state) {
case AI_MODE_STATE_IDLE: /* wait for a trigger */ break;
case AI_MODE_STATE_LISTEN: /* capture voice */ break;
default: break;
}
return OPRT_OK;
}

static OPERATE_RET my_mode_handle_event(AI_NOTIFY_EVENT_T *event) { return OPRT_OK; }

OPERATE_RET my_mode_register(void)
{
AI_MODE_HANDLE_T handle = {
.name = "my_mode",
.init = my_mode_init,
.deinit = my_mode_deinit,
.task = my_mode_task,
.handle_event = my_mode_handle_event,
.get_state = my_mode_get_state,
};
return ai_mode_register(AI_CHAT_MODE_CUSTOM_START, &handle);
}

See also