feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s
ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
// Actual command recognizer, message/report parsing and stop-readiness boundary.
|
||||
const fs = require('node:fs'), path = require('node:path');
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const read = p => fs.readFileSync(path.join(root, p), 'utf8');
|
||||
const src = read('components/application/service/conv_service/src/volc_conv.c');
|
||||
const json = path.join('E:/Codex/toolchains/esp-idf-v5.5.1/components/json/cJSON');
|
||||
function section(start, end) {
|
||||
const a = src.indexOf(start), b = src.indexOf(end, a + start.length);
|
||||
if (a < 0 || b < 0) throw new Error('Missing production section ' + start);
|
||||
return src.slice(a, b);
|
||||
}
|
||||
process.stdout.write(read('components/application/service/conv_service/inc/call_voice_end.h').replace('#pragma once', '') + '\n' +
|
||||
read('components/application/service/conv_service/src/call_voice_end.c').replace('#include "call_voice_end.h"', '') + '\n' +
|
||||
fs.readFileSync(path.join(json, 'cJSON.h'), 'utf8') + '\n' +
|
||||
fs.readFileSync(path.join(json, 'cJSON.c'), 'utf8').replace('#include "cJSON.h"', '') + String.raw`
|
||||
#include <assert.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
typedef void *volc_engine_t;
|
||||
typedef void *volc_hal_player_t;
|
||||
typedef struct {int unused;} volc_message_info_t;
|
||||
typedef struct {void *player_handle[1];} volc_hal_context_t;
|
||||
static volc_hal_context_t hal={{(void*)1}};
|
||||
static struct {void *engine;int wait_time;} conv_service;
|
||||
static bool is_stop,stop_requested,session_started_reported,rtc=true,player_quiet=true;
|
||||
static call_voice_end_t voice_end;
|
||||
static atomic_uint voice_finish_serial,last_voice_received_ms;
|
||||
static atomic_bool local_speech_active;
|
||||
static uint64_t now_ms;
|
||||
static cJSON *queue[8];static unsigned head,tail,turns,round_id;
|
||||
static void *platform_report_queue=(void*)1;
|
||||
#define pdTRUE 1
|
||||
#define VOLC_HAL_PLAYER_AUDIO 0
|
||||
#define VOLC_FUNCTION_CALL_EXEC 1
|
||||
#define VOLC_FUNCTION_CALL_TRIGGER 2
|
||||
#define LOGI(...) ((void)0)
|
||||
#define LOGE(...) ((void)0)
|
||||
static uint64_t volc_osal_get_time_ms(void){return now_ms;}
|
||||
static bool platform_runtime_rtc(void){return rtc;}
|
||||
static volc_hal_context_t *volc_get_global_hal_context(void){return &hal;}
|
||||
static bool volc_hal_player_voice_quiet(void *p,unsigned ms){assert(p&&(ms==600));return player_quiet;}
|
||||
static void volc_hal_player_play_data(void *p,const void *d,size_t n){(void)p;(void)d;(void)n;}
|
||||
static void volc_hal_player_finish_segment(void *p){(void)p;}
|
||||
static void call_idle_activity(void){}
|
||||
static void platform_conversation_observe(const char *type){(void)type;}
|
||||
static void __on_subtitle_message_received(const cJSON *j){(void)j;}
|
||||
static const char *volc_current_user_id(void *e,bool remote){(void)e;return remote?"bot":"user";}
|
||||
static int platform_conversation_turn(const char *s,const char *id,const char *text){assert(s&&id&&text);turns++;return 0;}
|
||||
static int platform_conversation_started(const char *ref){assert(ref);return 0;}
|
||||
static void aios_event_pub(int id,cJSON *j,void *u){(void)id;(void)u;cJSON_Delete(j);}
|
||||
static int xQueueSend(void *q,cJSON **p,unsigned ticks){(void)q;(void)ticks;if(tail-head==8)return 0;queue[tail++%8]=*p;return pdTRUE;}
|
||||
static int xQueueReceive(void *q,cJSON **p,unsigned ticks){(void)q;(void)ticks;if(head==tail)return 0;*p=queue[head++%8];return pdTRUE;}
|
||||
` + section('/* Called only by the conversation task,', '\nvoid conv_ai_service_init()') + String.raw`
|
||||
static void subtitle(const char *user,const char *text,bool definite,bool paragraph){
|
||||
cJSON *j=cJSON_CreateObject(),*list=cJSON_AddArrayToObject(j,"data"),*line=cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(j,"type","subtitle");cJSON_AddItemToArray(list,line);
|
||||
cJSON_AddStringToObject(line,"userId",user);cJSON_AddStringToObject(line,"text",text);
|
||||
cJSON_AddNumberToObject(line,"sequence",tail);cJSON_AddBoolToObject(line,"definite",definite);cJSON_AddBoolToObject(line,"paragraph",paragraph);
|
||||
cJSON_AddNumberToObject(line,"roundId",++round_id);
|
||||
char *s=cJSON_PrintUnformatted(j);cJSON_Delete(j);char b[1024]="subv0000";
|
||||
assert(strlen(s)+8<sizeof(b));strcpy(b+8,s);free(s);
|
||||
__on_volc_message_data(NULL,b,strlen(b),NULL,NULL);
|
||||
assert(!voice_end.pending); // receive callback never stops or mutates the FSM
|
||||
process_platform_reports();
|
||||
}
|
||||
static void reset(void){call_voice_end_reset(&voice_end);memset(&voice_user_subtitle,0,sizeof(voice_user_subtitle));now_ms=1000;atomic_store(&voice_finish_serial,0);atomic_store(&last_voice_received_ms,0);atomic_store(&local_speech_active,false);is_stop=stop_requested=false;player_quiet=true;}
|
||||
int main(void){
|
||||
const char *yes[]={"拜拜吧","拜拜","再见了","就聊到这把","就聊到这吧","今天就聊到这里吧","好了,今天我们就先聊到这儿吧。谢谢你,拜拜!","那就先不聊了","请挂断电话","结束这次聊天吧","我先走了","我要休息了","下次再聊"};
|
||||
const char *no[]={"不要拜拜","先别挂断","不要结束聊天","他说拜拜","她跟我说再见","你会说拜拜吗","拜拜是什么意思","就聊到这里吗?","\"拜拜\"","‘拜拜’","拜拜?","昨天就聊到这吧","好的拜拜之后继续聊","我还不想结束聊天","不聊了才怪","拜拜,不对,我还想聊","谢谢","今天的天气很好",NULL};
|
||||
for(size_t i=0;i<sizeof(yes)/sizeof(*yes);i++)assert(call_voice_end_command(yes[i]));
|
||||
for(size_t i=0;i<sizeof(no)/sizeof(*no);i++)assert(!call_voice_end_command(no[i]));
|
||||
char huge[2048];memset(huge,'x',sizeof(huge));huge[sizeof(huge)-1]=0;assert(!call_voice_end_command(huge));
|
||||
reset();subtitle("bot","拜拜吧",true,true);assert(!voice_end.pending);
|
||||
subtitle("other","拜拜吧",true,true);assert(!voice_end.pending);
|
||||
subtitle("user","拜拜吧",false,true);assert(!voice_end.pending);
|
||||
const char *interim="{\"type\":\"subtitle\",\"data\":[{\"userId\":\"user\",\"sequence\":3,\"roundId\":2,\"definite\":false,\"paragraph\":false,\"text\":\"拜拜\"}]}";
|
||||
for(int i=0;i<100;i++)__on_volc_message_data(NULL,interim,strlen(interim),NULL,NULL);
|
||||
assert(head==tail&&!voice_end.pending); // partial floods cannot consume control slots
|
||||
subtitle("user","拜拜吧",true,false);assert(!voice_end.pending);
|
||||
subtitle("user","拜拜,不对,我还想聊",true,true);assert(!voice_end.pending);
|
||||
cJSON *part=cJSON_CreateObject();cJSON_AddNumberToObject(part,"roundId",++round_id);cJSON_AddNumberToObject(part,"sequence",1);cJSON_AddBoolToObject(part,"definite",true);cJSON_AddBoolToObject(part,"paragraph",false);cJSON_AddStringToObject(part,"text","你跟我说一句,");
|
||||
process_voice_end_rtc_user(part);assert(!voice_end.pending);
|
||||
cJSON_SetNumberValue(cJSON_GetObjectItem(part,"sequence"),2);cJSON_SetBoolValue(cJSON_GetObjectItem(part,"paragraph"),true);cJSON_SetValuestring(cJSON_GetObjectItem(part,"text"),"再见");
|
||||
process_voice_end_rtc_user(part);assert(!voice_end.pending);cJSON_Delete(part);
|
||||
subtitle("user","就聊到这把",true,true);assert(voice_end.pending&&!voice_end_ready());
|
||||
uint32_t deadline=voice_end.requested_ms;now_ms=2000;
|
||||
call_voice_end_user(&voice_end,"就聊到这把",now_ms,0);assert(voice_end.requested_ms==deadline);
|
||||
atomic_store(&voice_finish_serial,1);now_ms=2600;player_quiet=false;assert(!voice_end_ready());
|
||||
player_quiet=true;atomic_store(&last_voice_received_ms,2500);assert(!voice_end_ready());
|
||||
now_ms=3800;assert(voice_end_ready());atomic_store(&local_speech_active,true);assert(!voice_end_ready());
|
||||
process_voice_end_transcript("subject","等一下,我还想问个问题",true);assert(!voice_end.pending);
|
||||
reset();subtitle("user","拜拜",true,true);now_ms=12999;assert(!voice_end_ready());now_ms=13000;assert(voice_end_ready());
|
||||
reset();subtitle("user","拜拜",true,true);is_stop=true;process_voice_end_transcript("subject","再见",true); // teardown cannot arm another close
|
||||
call_voice_end_reset(&voice_end);process_voice_end_transcript("subject","拜拜",true);assert(!voice_end.pending);
|
||||
reset();now_ms=UINT32_MAX-500;call_voice_end_user(&voice_end,"拜拜",now_ms,0);now_ms+=12000;assert(call_voice_end_due(&voice_end,(uint32_t)now_ms,0,false,false));
|
||||
reset();rtc=false;
|
||||
const char *msg="{\"type\":\"conversation.item.input_audio_transcription.completed\",\"event_id\":\"e1\",\"transcript\":\"拜拜吧\"}";
|
||||
__on_volc_message_data(NULL,msg,strlen(msg),NULL,NULL);assert(!voice_end.pending);process_platform_reports();assert(voice_end.pending);
|
||||
const char *done="{\"type\":\"response.audio.done\"}";
|
||||
__on_volc_message_data(NULL,done,strlen(done),NULL,NULL);assert(atomic_load(&voice_finish_serial)==1);
|
||||
now_ms+=2000;assert(voice_end_ready());
|
||||
puts("PASS voice end: actual RTC/WS parsing, complete user-only commands, negation/quotes/questions, playback wait, cancel, duplicate deadline, 12s fallback, clock wrap, teardown fencing");
|
||||
return 0;
|
||||
}
|
||||
`);
|
||||
Reference in New Issue
Block a user