fix(control_plane): preserve VAD generation on repeated PTT activation
This commit is contained in:
@@ -272,11 +272,12 @@ CP_TEST = r"""
|
||||
static ptt_runtime_t ptt;
|
||||
static domain_speech_endpoint_snapshot_t endpoint;
|
||||
static bool gate, change_generation;
|
||||
static int deactivated;
|
||||
static int deactivated, snapshot_error;
|
||||
static const char *last_reason;
|
||||
static ptt_runtime_t control_plane_ptt_snapshot(void) {return ptt;}
|
||||
static int domain_speech_endpoint_stop(void) {endpoint.active=false;return 0;}
|
||||
static int domain_speech_endpoint_get_snapshot(domain_speech_endpoint_snapshot_t *out) {
|
||||
if(snapshot_error) return snapshot_error;
|
||||
*out=endpoint; if(change_generation) {++ptt.generation;change_generation=false;} return 0;
|
||||
}
|
||||
static bool control_plane_local_wake_prompt_gate_active(void) {return gate;}
|
||||
@@ -288,6 +289,7 @@ static void control_plane_ptt_set_state(ptt_state_t state,const char *why) {(voi
|
||||
"""
|
||||
CP_MAIN = r"""
|
||||
int main(void) {
|
||||
test_activation();
|
||||
ticks=100; ptt=(ptt_runtime_t){.active=true,.state=PTT_STATE_STREAMING,.generation=7,.press_tick=1};
|
||||
endpoint=(domain_speech_endpoint_snapshot_t){.active=true,.ended=true,.generation=6};
|
||||
assert(!control_plane_service_ptt_lifecycle() && deactivated==0);
|
||||
@@ -298,11 +300,93 @@ int main(void) {
|
||||
ptt.active=true; ptt.state=PTT_STATE_STREAMING; endpoint.active=false;
|
||||
assert(!control_plane_service_ptt_lifecycle()); ticks=8001;
|
||||
assert(control_plane_service_ptt_lifecycle() && deactivated==2 && !strcmp(last_reason,"capture_timeout"));
|
||||
ptt.active=true; snapshot_error=ESP_ERR_TIMEOUT;
|
||||
assert(control_plane_service_ptt_lifecycle() && deactivated==3 && !strcmp(last_reason,"capture_timeout"));
|
||||
snapshot_error=0;
|
||||
ptt=(ptt_runtime_t){.state=PTT_STATE_IDLE}; endpoint.active=true;
|
||||
assert(!control_plane_service_ptt_lifecycle() && !endpoint.active);
|
||||
puts("PASS: actual control-plane C stale-generation rejection, prompt gate, concurrent cancel and 8s fallback");
|
||||
}
|
||||
"""
|
||||
CP_ACTIVATION_STUB = r"""
|
||||
#define s_ptt_runtime ptt
|
||||
#define CONTROL_PLANE_NETWORK_ACTION_CONVERSATION 1
|
||||
static int s_runtime_lock, pauses, resumes, expressions, notifications, readiness_calls;
|
||||
static bool s_ws_start_requested, ota_allowed=true;
|
||||
static esp_err_t readiness_error;
|
||||
static void (*readiness_hook)(void);
|
||||
static bool control_plane_ota_business_interactions_allowed(void) {return ota_allowed;}
|
||||
static void control_plane_local_wake_pause_for_foreground(void) {++pauses;}
|
||||
static void control_plane_local_wake_resume_after_foreground(void) {++resumes;}
|
||||
static esp_err_t control_plane_network_feedback_require_ready(int action) {
|
||||
(void)action; ++readiness_calls; if(readiness_hook) readiness_hook(); return readiness_error;
|
||||
}
|
||||
static void control_plane_ptt_transition_locked(ptt_state_t state,const char *reason) {
|
||||
(void)reason; ptt.state=state; ptt.state_enter_tick=ticks;
|
||||
}
|
||||
static void control_plane_ptt_log_transition(ptt_state_t a,ptt_state_t b,uint32_t gen,const char *why) {
|
||||
(void)a;(void)b;(void)gen;(void)why;
|
||||
}
|
||||
static void control_plane_request_ptt_expression(domain_expression_id_t id,domain_expression_trigger_t trigger) {
|
||||
(void)id;(void)trigger;++expressions;
|
||||
}
|
||||
static void control_plane_notify_wifi_state_task(void) {++notifications;}
|
||||
"""
|
||||
CP_ACTIVATION_TEST = r"""
|
||||
static void concurrent_activation(void) {
|
||||
readiness_hook=NULL;
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_LOCAL_WAKE);
|
||||
}
|
||||
static void test_activation(void) {
|
||||
const ptt_state_t stages[]={PTT_STATE_WAIT_WIFI,PTT_STATE_WAIT_WS,PTT_STATE_WAIT_START_SENT,
|
||||
PTT_STATE_WAIT_LISTENING,PTT_STATE_WAIT_WAKE_PROMPT,PTT_STATE_WAIT_SPEECH_STARTED,
|
||||
PTT_STATE_STREAMING};
|
||||
ticks=100; ptt=(ptt_runtime_t){.state=PTT_STATE_IDLE};
|
||||
ota_allowed=false;
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON);
|
||||
assert(!ptt.active && !pauses && !readiness_calls);
|
||||
ota_allowed=true; readiness_error=ESP_ERR_INVALID_STATE;
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON);
|
||||
assert(!ptt.active && resumes==1);
|
||||
readiness_error=ESP_OK;
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON);
|
||||
assert(ptt.active && ptt.generation==1 && ptt.press_tick==100 && expressions==1);
|
||||
assert(ptt.state==PTT_STATE_WAIT_WIFI && s_ws_start_requested && notifications==1);
|
||||
for(unsigned i=0;i<sizeof(stages)/sizeof(stages[0]);++i) {
|
||||
ptt.state=stages[i]; ptt.endpoint_start_attempted=true;
|
||||
ptt.start_action_sent=true; ptt.speech_action_sent=true;
|
||||
ptt_runtime_t before=ptt;
|
||||
int previous_pauses=pauses, previous_ready=readiness_calls;
|
||||
ticks+=1000;
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_LOCAL_WAKE);
|
||||
assert(!memcmp(&before,&ptt,sizeof(ptt)));
|
||||
assert(pauses==previous_pauses && readiness_calls==previous_ready && expressions==1);
|
||||
}
|
||||
/* 重复触发后仍接收原代次端点;窗口也不能被续期。 */
|
||||
endpoint=(domain_speech_endpoint_snapshot_t){.active=true,.ended=true,.generation=ptt.generation};
|
||||
assert(control_plane_service_ptt_lifecycle() && !strcmp(last_reason,"vad_endpoint"));
|
||||
ptt=(ptt_runtime_t){.active=true,.state=PTT_STATE_STREAMING,.generation=9,.press_tick=100};
|
||||
endpoint=(domain_speech_endpoint_snapshot_t){0}; ticks=8099;
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON);
|
||||
assert(!control_plane_service_ptt_lifecycle()); ++ticks;
|
||||
assert(control_plane_service_ptt_lifecycle() && !strcmp(last_reason,"capture_timeout"));
|
||||
const ptt_state_t teardown[]={PTT_STATE_TEARDOWN_LOCAL,PTT_STATE_TEARDOWN_REMOTE};
|
||||
for(unsigned i=0;i<2;++i) {
|
||||
ptt=(ptt_runtime_t){.state=teardown[i],.generation=10};
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON);
|
||||
assert(ptt.active && ptt.generation==11 && ptt.pending_press_after_teardown);
|
||||
assert(ptt.state==teardown[i] && !ptt.endpoint_start_attempted);
|
||||
}
|
||||
ptt=(ptt_runtime_t){.state=PTT_STATE_IDLE,.generation=20};
|
||||
readiness_hook=concurrent_activation;
|
||||
int previous_expressions=expressions;
|
||||
control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON);
|
||||
assert(ptt.generation==21 && expressions==previous_expressions+1);
|
||||
assert(ptt.listening_ui_trigger==DOMAIN_EXPRESSION_TRIGGER_LOCAL_WAKE);
|
||||
deactivated=0;
|
||||
puts("PASS: actual PTT activation C ignores repeats, preserves endpoint/window, queues teardown and rejects concurrent duplicate");
|
||||
}
|
||||
"""
|
||||
PENDING_TEST = r"""
|
||||
#include "common.h"
|
||||
typedef struct {bool uplink_send_in_flight;void *uplink_send_queue;} multimodal_audio_ctx_t;
|
||||
@@ -362,7 +446,23 @@ puts("PASS: disabled domain C fallback"); }
|
||||
cp_types=re.search(r'typedef enum \{\s+PTT_STATE_IDLE.*?} ptt_runtime_t;',cp_source,re.S).group(0)
|
||||
# Hide facade declarations only for test doubles; production function is unchanged.
|
||||
cp_test=CP_TEST.replace('static int domain_speech_endpoint_', 'int domain_speech_endpoint_')
|
||||
compile_run(tmp, 'control_plane', CP_STUB + cp_types + cp_test + extract(cp_source, 'control_plane_service_ptt_lifecycle') + CP_MAIN)
|
||||
cp_lifecycle=extract(cp_source, 'control_plane_service_ptt_lifecycle')
|
||||
cp_activation='\n'.join(extract(cp_source, n) for n in [
|
||||
'control_plane_ptt_has_active_turn_locked', 'control_plane_set_ptt_active_with_trigger'])
|
||||
compile_run(tmp, 'control_plane', CP_STUB + cp_types + cp_test + CP_ACTIVATION_STUB +
|
||||
cp_activation + cp_lifecycle + CP_ACTIVATION_TEST + CP_MAIN)
|
||||
compile_run(tmp, 'control_plane_disabled', CP_STUB.replace(
|
||||
'#define CONFIG_TQ_SPEECH_ENDPOINT_ENABLE 1', '#define CONFIG_TQ_SPEECH_ENDPOINT_ENABLE 0') +
|
||||
cp_types + cp_test + CP_ACTIVATION_STUB + cp_activation + cp_lifecycle + r'''
|
||||
int main(void) {
|
||||
ticks=100; control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_PTT_BUTTON);
|
||||
ptt.state=PTT_STATE_STREAMING;
|
||||
ticks=8100; control_plane_set_ptt_active_with_trigger(true,DOMAIN_EXPRESSION_TRIGGER_LOCAL_WAKE);
|
||||
assert(ptt.generation==1 && ptt.press_tick==100);
|
||||
assert(control_plane_service_ptt_lifecycle() && !strcmp(last_reason,"capture_timeout"));
|
||||
puts("PASS: VAD disabled control-plane C preserves generation and 8s fallback on repeated activation");
|
||||
}
|
||||
''')
|
||||
compile_run(tmp, 'stop', STOP_STUB + '\n'.join(extract(AUDIO, n) for n in ['audio_uplink_tail_pending', 'audio_flush_uplink_tail', 'multimodal_audio_stop_uplink']) + STOP_START_STUB + extract(AUDIO, 'multimodal_audio_start_uplink') + STOP_TEST)
|
||||
|
||||
# Integration guards supplement C execution; they do not replace device timing tests.
|
||||
|
||||
Reference in New Issue
Block a user