Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fad31c37a | ||
|
|
83ef72839e | ||
|
|
82a419f812 | ||
|
|
1f55943c29 | ||
|
|
beadb79368 | ||
|
|
5f5604e45b | ||
|
|
1cd3c63890 | ||
|
|
d3d1b6e7fb | ||
|
|
d07b3f6771 | ||
|
|
cfdb193e75 | ||
|
|
8b01debe4d | ||
|
|
1c9f16da39 | ||
|
|
5645a0a36b | ||
|
|
3185d8f5e3 | ||
|
|
c34533f32a | ||
|
|
14c0065bb1 | ||
|
|
768ad0e47f | ||
|
|
913c5b9f71 | ||
|
|
d8b2114d0e | ||
|
|
381db92214 | ||
|
|
d6c31ffe31 | ||
|
|
64fdeb9aaa | ||
|
|
fe1ca9899f | ||
|
|
fed4a5300a | ||
|
|
e49bb9888f | ||
|
|
21734402d5 |
+1
-3
@@ -1,8 +1,6 @@
|
||||
@echo off
|
||||
set CURDIR=%~dp0
|
||||
set time0=%time: =0%
|
||||
set hour=%time0:~0,2%
|
||||
set appbuildtime=%date:~0,4%%date:~5,2%%date:~8,2%%hour%%time:~3,2%%time:~6,2%
|
||||
for /f %%i in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMddHHmmss"') do set appbuildtime=%%i
|
||||
set appbuildrev=NULL
|
||||
set appbuildmodel=EG800AKCN_11LG
|
||||
set appbuildver=beta1
|
||||
|
||||
@@ -31,8 +31,39 @@ extern int fota_start;
|
||||
#define KEY_LONG_PRESS_3S 3000/5
|
||||
#define KEY_LONG_PRESS_5S 5000/5
|
||||
#define KEY_LONG_PRESS_10S 10000/5
|
||||
#define NFC_TX_CLICK_GAP 2000/5
|
||||
#define NFC_TX_CLICK_COUNT 5
|
||||
|
||||
static unsigned char key_down_event = 0;
|
||||
static unsigned char nfc_tx_click_count = 0;
|
||||
static u32 nfc_tx_last_click_time = 0;
|
||||
|
||||
extern void app_nfc_tx_mode_start(void);
|
||||
|
||||
static int key_mic_register_short_click(void)
|
||||
{
|
||||
u32 now = ql_rtos_get_systicks();
|
||||
|
||||
if (nfc_tx_click_count == 0 ||
|
||||
(u32)(now - nfc_tx_last_click_time) > NFC_TX_CLICK_GAP) {
|
||||
nfc_tx_click_count = 1;
|
||||
} else {
|
||||
nfc_tx_click_count++;
|
||||
}
|
||||
nfc_tx_last_click_time = now;
|
||||
|
||||
printf("NFC TX key clicks:%u/%u\r\n",
|
||||
nfc_tx_click_count, NFC_TX_CLICK_COUNT);
|
||||
if (nfc_tx_click_count >= NFC_TX_CLICK_COUNT) {
|
||||
nfc_tx_click_count = NFC_TX_CLICK_COUNT;
|
||||
app_nfc_tx_mode_start();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Keep the first click's existing voice action. Suppress clicks 2-4 so
|
||||
* a rapid NFC gesture does not repeatedly start/interrupt voice work. */
|
||||
return nfc_tx_click_count > 1;
|
||||
}
|
||||
|
||||
static void key_press_mic(void *args)
|
||||
{
|
||||
@@ -115,17 +146,21 @@ static void key_thread_entry(void *parameter)
|
||||
key_press_mic_status = 0;
|
||||
|
||||
if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S){
|
||||
nfc_tx_click_count = 0;
|
||||
//app_play_tips_file(10);
|
||||
// power_down_status = 1;
|
||||
ql_rtos_task_sleep_s(3);
|
||||
ql_power_down(1);
|
||||
}
|
||||
else if(ql_rtos_get_systicks() - key_press_mic_time > KEY_WAIT_TIME){
|
||||
int nfc_click_handled = key_mic_register_short_click();
|
||||
|
||||
//休眠唤醒其它线程
|
||||
if(app_get_app_sleep_flag()==0){
|
||||
app_set_app_operation(1);
|
||||
}
|
||||
|
||||
if (!nfc_click_handled) {
|
||||
int mic_pdp = app_get_data_call_net_status();
|
||||
int mic_mqtt = get_mqtt_connect_status();
|
||||
printf("[NET-GATE] mic pdp=%d mqtt=%d action=%s\r\n",
|
||||
@@ -139,6 +174,7 @@ static void key_thread_entry(void *parameter)
|
||||
app_play_tips_file(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
key_press_mic_time = ql_rtos_get_systicks();
|
||||
key_press_mic_counter = 0;
|
||||
@@ -149,7 +185,11 @@ static void key_thread_entry(void *parameter)
|
||||
}
|
||||
|
||||
if(ql_rtos_get_systicks() - key_press_mic_time > KEY_LONG_PRESS_3S && power_down_status==0){
|
||||
nfc_tx_click_count = 0;
|
||||
power_down_status = 1;
|
||||
/* Shutdown audio must own the codec. Stop VAD and any
|
||||
* interrupted TTS before queueing the power-off prompt. */
|
||||
stop_player_func();
|
||||
app_play_tips_file(10);
|
||||
power_down_status = 2;
|
||||
//ql_rtos_task_sleep_s(3);
|
||||
|
||||
@@ -34,6 +34,7 @@ const char *mp3_file[] ={
|
||||
"volume_up_zh.mp3",//16
|
||||
"volume_down_zh.mp3",//17
|
||||
"ai_active_zh.mp3",//18
|
||||
"barge_in_cute.mp3",//19 对话打断提示音
|
||||
};
|
||||
|
||||
//http://banban.api.talkingq.com:8080/assets/roles/banban/tts_error.mp3
|
||||
@@ -183,9 +184,11 @@ void app_file_check_download(void)
|
||||
char filename_buff[64]={0x00};
|
||||
int ret = 0, cnt = 0;
|
||||
|
||||
for(index=1;index<19;index++){
|
||||
for(index=1;index<20;index++){
|
||||
|
||||
if(index==4||index==5||index==6||index==7||index==8){
|
||||
/* barge_in_cute.mp3 is shipped in customer_fs and is not hosted by the
|
||||
* legacy prompt download service. */
|
||||
if(index==4||index==5||index==6||index==7||index==8||index==19){
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -208,9 +211,11 @@ void app_file_check_del(void)
|
||||
int index = 1;
|
||||
char filename_buff[64]={0x00};
|
||||
|
||||
for(index=1;index<19;index++){
|
||||
for(index=1;index<20;index++){
|
||||
|
||||
if(index==4||index==5||index==6||index==7||index==8){
|
||||
/* Keep the bundled barge-in prompt across app-only FOTA. Unlike the
|
||||
* server-managed prompts, it cannot be restored by the downloader. */
|
||||
if(index==4||index==5||index==6||index==7||index==8||index==19){
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ static unsigned char mp3_player_status = 0;
|
||||
QFILE *local_mp3_file_fd=NULL;
|
||||
|
||||
static int mp3_player_end_frame = 0;
|
||||
static int mp3_player_last_result = 0;
|
||||
|
||||
int current_sample_rate = 0;
|
||||
static audio_track_handle track_hd;
|
||||
@@ -278,9 +279,10 @@ u32 local_mp3_data_fetch(const char *uri, uint8_t *buffer, u32 length)
|
||||
return read_bytes;
|
||||
}
|
||||
|
||||
size_t url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
|
||||
int url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
|
||||
{
|
||||
int resp_status = -1;
|
||||
int result = -1;
|
||||
uint32_t nw = length;
|
||||
uint32_t page_pos = 0;
|
||||
uint32_t content_length = 0;
|
||||
@@ -354,6 +356,9 @@ size_t url_mp3_data_fetch(const char *uri, uint8_t *buffer, size_t length)
|
||||
}
|
||||
|
||||
printf("end read:%d, %d\n", total_length, session->content_length);
|
||||
if(total_length == session->content_length){
|
||||
result = 0;
|
||||
}
|
||||
|
||||
__exit:
|
||||
|
||||
@@ -363,12 +368,12 @@ __exit:
|
||||
session = NULL;
|
||||
}
|
||||
|
||||
if(total_length<content_length){
|
||||
if(result != 0 || total_length < content_length){
|
||||
total_length = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -390,11 +395,24 @@ int aofei_mp3_player_init(void)
|
||||
void aofei_mp3_player(char *file_name)
|
||||
{
|
||||
int ret = 0;
|
||||
uint8_t *mp3_buff = malloc(100*1024);
|
||||
uint8_t *mp3_buff = NULL;
|
||||
|
||||
mp3_player_last_result = -1;
|
||||
if(file_name == NULL){
|
||||
printf("mp3 player file name is null\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mp3_buff = malloc(100*1024);
|
||||
if(mp3_buff == NULL){
|
||||
printf("mp3 player buffer malloc failed\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memset(mp3_buff, 0, 100*1024);
|
||||
//直接下载到一个100k的buffer
|
||||
if(strstr(file_name, "http:")){//播放url
|
||||
if(strncmp(file_name, "http://", strlen("http://")) == 0 ||
|
||||
strncmp(file_name, "https://", strlen("https://")) == 0){//播放url
|
||||
ret = url_mp3_data_fetch(file_name, mp3_buff, 100*1024);
|
||||
}else if(strstr(file_name, "test.mp3")){//播放本地音频
|
||||
total_length = test_data_size;
|
||||
@@ -403,10 +421,18 @@ void aofei_mp3_player(char *file_name)
|
||||
total_length = local_mp3_data_fetch(file_name, mp3_buff, 100*1024);
|
||||
}
|
||||
|
||||
if(ret < 0 || total_length == 0){
|
||||
ret = -1;
|
||||
goto __exit;
|
||||
}
|
||||
|
||||
mp3_player_end_frame = 0;
|
||||
|
||||
|
||||
if(aofei_mp3_player_init()==-1)return;
|
||||
if(aofei_mp3_player_init()==-1){
|
||||
ret = -1;
|
||||
goto __exit;
|
||||
}
|
||||
|
||||
if(ret>=0){
|
||||
aofei_mp3_decoder->read_ptr = mp3_buff;
|
||||
@@ -457,6 +483,7 @@ void aofei_mp3_player(char *file_name)
|
||||
app_codec_pa_close();
|
||||
}
|
||||
|
||||
__exit:
|
||||
if(aofei_mp3_decoder!=NULL){
|
||||
mp3_decoder_delete(aofei_mp3_decoder);
|
||||
aofei_mp3_decoder = NULL;
|
||||
@@ -480,6 +507,13 @@ void aofei_mp3_player(char *file_name)
|
||||
total_length = 0;
|
||||
mp3_player_end_frame = 0;
|
||||
mp3_player_status = 0;
|
||||
mp3_player_last_result = ret;
|
||||
}
|
||||
|
||||
/* 播放调用是同步的,队列线程可在返回后读取本次结果决定是否使用本地兜底。 */
|
||||
int aofei_mp3_player_get_result(void)
|
||||
{
|
||||
return mp3_player_last_result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#if 1
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "ql_rtos.h"
|
||||
#include "ql_application.h"
|
||||
#include "ql_audio.h"
|
||||
@@ -7,14 +8,25 @@
|
||||
#include "ql_gpio.h"
|
||||
#include "app_inc.h"
|
||||
#include "ql_fs.h"
|
||||
#include "DP1312EA.h"
|
||||
#include "TypeA.h"
|
||||
#include "LPCD.h"
|
||||
#include "hal_api.h"
|
||||
|
||||
#define DP1312EA_PIN_IRQ GPIO_PIN_NO_85
|
||||
#define NFC_TX_MODE_DURATION_MS 30000U
|
||||
#define NFC_TICKS_PER_MS_DIVISOR 5U
|
||||
|
||||
extern int fota_start;
|
||||
extern int audio_packet_type;
|
||||
extern volatile int tts_playing;
|
||||
extern int g_testmode;
|
||||
extern uint8_t get_sleep_mode(void);
|
||||
extern int get_mqtt_connect_status(void);
|
||||
extern bool vad_audio_is_recording(void);
|
||||
extern int xiaoice_get_play_counter(void);
|
||||
|
||||
static ql_sem_t nfc_irq_sem = NULL;
|
||||
#define DP1312EA_PIN_IRQ GPIO_PIN_NO_85
|
||||
|
||||
// NFC 任务状态
|
||||
typedef enum {
|
||||
@@ -25,10 +37,330 @@ typedef enum {
|
||||
static volatile nfc_state_t g_nfc_state = NFC_STATE_LPCD_WAIT;
|
||||
static volatile int g_poll_fail_count = 0;
|
||||
static volatile int g_nfc_irq_trigger = 0;
|
||||
static volatile u8 g_nfc_tx_mode_requested = 0;
|
||||
static volatile u32 g_nfc_tx_mode_deadline = 0;
|
||||
|
||||
#define NFC_CARD_FRAME_MAX 64
|
||||
#define NFC_CARD_TX_POLLS 20
|
||||
#define NFC_CARD_RX_POLLS 200
|
||||
#define NFC_CARD_SESSION_POLL_MS 1
|
||||
#define NFC_TYPE2_PAGE_SIZE 4
|
||||
#define NFC_TYPE2_READ_SIZE 16
|
||||
#define NFC_TYPE2_MEMORY_SIZE 160
|
||||
#define NFC_TYPE2_NDEF_OFFSET 16
|
||||
|
||||
#define NFC_IRQ_TX 0x40
|
||||
#define NFC_IRQ_RX 0x20
|
||||
#define NFC_IRQ_ERR 0x02
|
||||
|
||||
extern int hal_os_get_device_sn(char *pdevice_sn);
|
||||
extern s8 Test_NTAG213(void);
|
||||
|
||||
static u8 nfc_type2_memory[NFC_TYPE2_MEMORY_SIZE];
|
||||
|
||||
static u8 nfc_tx_mode_is_active(void)
|
||||
{
|
||||
u32 now;
|
||||
|
||||
if (!g_nfc_tx_mode_requested) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
now = ql_rtos_get_systicks();
|
||||
if ((s32)(now - g_nfc_tx_mode_deadline) >= 0) {
|
||||
g_nfc_tx_mode_requested = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void app_nfc_tx_mode_start(void)
|
||||
{
|
||||
g_nfc_tx_mode_deadline = ql_rtos_get_systicks() +
|
||||
(NFC_TX_MODE_DURATION_MS / NFC_TICKS_PER_MS_DIVISOR);
|
||||
g_nfc_tx_mode_requested = 1;
|
||||
|
||||
if (nfc_irq_sem != NULL) {
|
||||
ql_rtos_semaphore_release(nfc_irq_sem);
|
||||
}
|
||||
|
||||
printf("NFC TX mode requested for 30 seconds\r\n");
|
||||
}
|
||||
|
||||
static void nfc_card_emulation_log_status(const char *reason)
|
||||
{
|
||||
printf("NFC card emulation %s: cmd=%02X irq=%02X err=%02X status1=%02X status2=%02X fifo=%02X txm=%02X rxm=%02X framing=%02X\r\n",
|
||||
reason,
|
||||
DP1312EA_ReadRawRC(CommandReg),
|
||||
DP1312EA_ReadRawRC(ComIrqReg),
|
||||
DP1312EA_ReadRawRC(ErrorReg),
|
||||
DP1312EA_ReadRawRC(Status1Reg),
|
||||
DP1312EA_ReadRawRC(Status2Reg),
|
||||
DP1312EA_ReadRawRC(FIFOLevelReg),
|
||||
DP1312EA_ReadRawRC(TxModeReg),
|
||||
DP1312EA_ReadRawRC(RxModeReg),
|
||||
DP1312EA_ReadRawRC(BitFramingReg));
|
||||
}
|
||||
|
||||
static u8 nfc_card_emulation_read_frame(u8 *frame, u8 capacity)
|
||||
{
|
||||
u8 fifo_len = DP1312EA_ReadRawRC(FIFOLevelReg) & 0x7F;
|
||||
u8 read_len = fifo_len;
|
||||
|
||||
if (read_len > capacity) {
|
||||
read_len = capacity;
|
||||
}
|
||||
|
||||
if (read_len > 0) {
|
||||
DP1312EA_ReadFIFO(frame, read_len);
|
||||
}
|
||||
|
||||
while (read_len < fifo_len) {
|
||||
(void)DP1312EA_ReadRawRC(FIFODataReg);
|
||||
read_len++;
|
||||
}
|
||||
|
||||
return fifo_len > capacity ? capacity : fifo_len;
|
||||
}
|
||||
|
||||
static void nfc_card_emulation_log_frame(const char *prefix,
|
||||
const u8 *frame,
|
||||
u8 length)
|
||||
{
|
||||
u8 i;
|
||||
|
||||
printf("NFC %s (%u):", prefix, length);
|
||||
for (i = 0; i < length; i++) {
|
||||
printf(" %02X", frame[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
static u8 nfc_card_emulation_send_frame(const u8 *frame, u8 length)
|
||||
{
|
||||
u16 poll_count;
|
||||
u8 irq;
|
||||
|
||||
/* AutoColl has activated target mode and changed to Transceive. Keep it
|
||||
* active: writing Idle here clears TargetActivated in Status2Reg. */
|
||||
DP1312EA_WriteRawRC(ComIrqReg, 0x7F);
|
||||
DP1312EA_SetBitMask(FIFOLevelReg, 0x80);
|
||||
DP1312EA_SetBitMask(TxModeReg, 0x80);
|
||||
DP1312EA_SetBitMask(RxModeReg, 0x80);
|
||||
DP1312EA_WriteRawRC(BitFramingReg, 0x00);
|
||||
DP1312EA_WriteFIFO((u8 *)frame, length);
|
||||
DP1312EA_SetBitMask(BitFramingReg, 0x80);
|
||||
|
||||
for (poll_count = 0; poll_count < NFC_CARD_TX_POLLS; poll_count++) {
|
||||
irq = DP1312EA_ReadRawRC(ComIrqReg);
|
||||
if ((irq & NFC_IRQ_TX) != 0) {
|
||||
DP1312EA_WriteRawRC(BitFramingReg, 0x00);
|
||||
return 1;
|
||||
}
|
||||
if ((irq & NFC_IRQ_ERR) != 0) {
|
||||
break;
|
||||
}
|
||||
ql_rtos_task_sleep_ms(NFC_CARD_SESSION_POLL_MS);
|
||||
}
|
||||
|
||||
DP1312EA_WriteRawRC(BitFramingReg, 0x00);
|
||||
nfc_card_emulation_log_status("frame transmit failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nfc_type2_build_ndef(void)
|
||||
{
|
||||
char device_id[32] = {0};
|
||||
char serial_number[32] = {0};
|
||||
char json[128];
|
||||
int json_len;
|
||||
u8 *ndef;
|
||||
u8 record_len;
|
||||
|
||||
if (hal_os_get_device_id(device_id) != 0) {
|
||||
strcpy(device_id, "unknown");
|
||||
}
|
||||
if (hal_os_get_device_sn(serial_number) != 0) {
|
||||
strcpy(serial_number, "unknown");
|
||||
}
|
||||
|
||||
json_len = snprintf(json, sizeof(json),
|
||||
"{\"device_id\":\"%s\",\"serial_number\":\"%s\"}",
|
||||
device_id, serial_number);
|
||||
if (json_len < 0) {
|
||||
json_len = 0;
|
||||
} else if (json_len >= (int)sizeof(json)) {
|
||||
json_len = sizeof(json) - 1;
|
||||
}
|
||||
|
||||
memset(nfc_type2_memory, 0, sizeof(nfc_type2_memory));
|
||||
|
||||
/* Static UID/manufacturer pages followed by a read-only Type 2 CC. */
|
||||
nfc_type2_memory[0] = 0x08;
|
||||
nfc_type2_memory[1] = 0xAA;
|
||||
nfc_type2_memory[2] = 0xBB;
|
||||
nfc_type2_memory[3] = 0xCC;
|
||||
nfc_type2_memory[12] = 0xE1;
|
||||
nfc_type2_memory[13] = 0x10;
|
||||
nfc_type2_memory[14] = 0x12; /* 144 data bytes (18 x 8). */
|
||||
nfc_type2_memory[15] = 0x0F; /* Read-only access. */
|
||||
|
||||
ndef = &nfc_type2_memory[NFC_TYPE2_NDEF_OFFSET];
|
||||
record_len = (u8)(7 + json_len);
|
||||
ndef[0] = 0x03; /* NDEF Message TLV. */
|
||||
ndef[1] = record_len;
|
||||
ndef[2] = 0xD1; /* MB, ME, SR, TNF well-known. */
|
||||
ndef[3] = 0x01; /* Type length. */
|
||||
ndef[4] = (u8)(3 + json_len); /* Text status/lang plus JSON. */
|
||||
ndef[5] = 'T';
|
||||
ndef[6] = 0x02; /* UTF-8, language length 2. */
|
||||
ndef[7] = 'e';
|
||||
ndef[8] = 'n';
|
||||
memcpy(&ndef[9], json, json_len);
|
||||
ndef[9 + json_len] = 0xFE; /* Terminator TLV. */
|
||||
|
||||
printf("NFC Type 2 NDEF: %s\r\n", json);
|
||||
}
|
||||
|
||||
static void nfc_type2_read_pages(u8 start_page, u8 *response)
|
||||
{
|
||||
u16 start = (u16)start_page * NFC_TYPE2_PAGE_SIZE;
|
||||
u8 i;
|
||||
|
||||
for (i = 0; i < NFC_TYPE2_READ_SIZE; i++) {
|
||||
u16 offset = start + i;
|
||||
response[i] = offset < NFC_TYPE2_MEMORY_SIZE
|
||||
? nfc_type2_memory[offset]
|
||||
: 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_card_emulation_configure_type2(void)
|
||||
{
|
||||
static const u8 card_config[] = {
|
||||
0x04, 0x00, /* ATQA: single-size (4-byte) UID */
|
||||
0xAA, 0xBB, 0xCC, /* NFCID1; first byte 08 is added by PN512 */
|
||||
0x00, /* SEL_RES / SAK: Type 2, no ISO-DEP */
|
||||
0x01, 0xFE, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0xFF, 0xFF,
|
||||
0x88
|
||||
};
|
||||
|
||||
DP1312EA_PcdConfigISOType('K');
|
||||
ql_rtos_task_sleep_ms(2);
|
||||
DP1312EA_WriteFIFO((u8 *)card_config, sizeof(card_config));
|
||||
ql_rtos_task_sleep_ms(2);
|
||||
DP1312EA_WriteRawRC(CommandReg, DP1312EA_MEM);
|
||||
DP1312EA_WriteRawRC(CommandReg, DP1312EA_AUTOCOLL);
|
||||
}
|
||||
|
||||
static void nfc_card_emulation_start(void)
|
||||
{
|
||||
DP1312EA_WriteRawRC(CommandReg, DP1312EA_IDLE);
|
||||
DP1312EA_WriteRawRC(ComIrqReg, 0x7F);
|
||||
DP1312EA_WriteRawRC(DivIrqReg, 0x7F);
|
||||
DP1312EA_SetBitMask(FIFOLevelReg, 0x80);
|
||||
|
||||
nfc_type2_build_ndef();
|
||||
nfc_card_emulation_configure_type2();
|
||||
nfc_card_emulation_log_status("started");
|
||||
printf("NFC Type 2 tag: ATQA=04 00, UID=08 AA BB CC, SAK=00\r\n");
|
||||
}
|
||||
|
||||
static void nfc_card_emulation_task(void)
|
||||
{
|
||||
u8 frame[NFC_CARD_FRAME_MAX];
|
||||
u8 response[NFC_TYPE2_READ_SIZE];
|
||||
u8 frame_len;
|
||||
u16 poll_count;
|
||||
u8 last_command = 0xFF;
|
||||
u8 command;
|
||||
|
||||
nfc_card_emulation_start();
|
||||
|
||||
while (nfc_tx_mode_is_active()) {
|
||||
command = DP1312EA_ReadRawRC(CommandReg) & 0x0F;
|
||||
|
||||
if (command != last_command) {
|
||||
nfc_card_emulation_log_status("state changed");
|
||||
last_command = command;
|
||||
}
|
||||
|
||||
if (command == DP1312EA_TRANSCEIVE) {
|
||||
frame_len = nfc_card_emulation_read_frame(frame, sizeof(frame));
|
||||
while (nfc_tx_mode_is_active() &&
|
||||
frame_len >= 2 && frame[0] == 0x30) {
|
||||
nfc_card_emulation_log_frame("Type 2 READ RX", frame,
|
||||
frame_len);
|
||||
nfc_type2_read_pages(frame[1], response);
|
||||
if (!nfc_card_emulation_send_frame(response,
|
||||
sizeof(response))) {
|
||||
break;
|
||||
}
|
||||
printf("NFC Type 2 READ page %u TX complete\r\n", frame[1]);
|
||||
nfc_card_emulation_log_frame("Type 2 READ TX", response,
|
||||
sizeof(response));
|
||||
|
||||
DP1312EA_WriteRawRC(ComIrqReg, NFC_IRQ_TX);
|
||||
for (poll_count = 0;
|
||||
poll_count < NFC_CARD_RX_POLLS &&
|
||||
nfc_tx_mode_is_active();
|
||||
poll_count++) {
|
||||
u8 irq = DP1312EA_ReadRawRC(ComIrqReg);
|
||||
|
||||
if ((irq & NFC_IRQ_ERR) != 0) {
|
||||
nfc_card_emulation_log_status("Type 2 receive error");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((irq & NFC_IRQ_RX) != 0 &&
|
||||
(DP1312EA_ReadRawRC(FIFOLevelReg) & 0x7F) != 0) {
|
||||
frame_len = nfc_card_emulation_read_frame(
|
||||
frame, sizeof(frame));
|
||||
break;
|
||||
}
|
||||
|
||||
ql_rtos_task_sleep_ms(NFC_CARD_SESSION_POLL_MS);
|
||||
}
|
||||
|
||||
if (poll_count == NFC_CARD_RX_POLLS) {
|
||||
nfc_card_emulation_log_status("Type 2 receive timeout");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((DP1312EA_ReadRawRC(ComIrqReg) & NFC_IRQ_ERR) != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_len > 0 && !(frame_len >= 2 && frame[0] == 0x30)) {
|
||||
nfc_card_emulation_log_frame("Type 2 session end RX", frame,
|
||||
frame_len);
|
||||
}
|
||||
|
||||
nfc_card_emulation_log_status("session complete");
|
||||
nfc_card_emulation_start();
|
||||
last_command = DP1312EA_AUTOCOLL;
|
||||
} else if (command != DP1312EA_AUTOCOLL) {
|
||||
nfc_card_emulation_log_status("unexpected command");
|
||||
nfc_card_emulation_start();
|
||||
last_command = DP1312EA_AUTOCOLL;
|
||||
}
|
||||
|
||||
ql_rtos_task_sleep_ms(1);
|
||||
}
|
||||
|
||||
DP1312EA_WriteRawRC(CommandReg, DP1312EA_IDLE);
|
||||
DP1312EA_WriteRawRC(ComIrqReg, 0x7F);
|
||||
DP1312EA_SetBitMask(FIFOLevelReg, 0x80);
|
||||
printf("NFC TX mode expired, restoring reader mode\r\n");
|
||||
}
|
||||
|
||||
static void nfc_irq_wake(void *args)
|
||||
{
|
||||
unsigned char level = 0;
|
||||
PIN_LEVEL_E level = PIN_LEVEL_LOW;
|
||||
|
||||
ql_eint_disable_wakeup(DP1312EA_PIN_IRQ);
|
||||
ql_gpio_get_level(DP1312EA_PIN_IRQ, &level);
|
||||
@@ -36,6 +368,10 @@ static void nfc_irq_wake(void *args)
|
||||
|
||||
ql_eint_enable_wakeup(DP1312EA_PIN_IRQ, PIN_RISING_EDGE);
|
||||
|
||||
if (g_nfc_tx_mode_requested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(g_nfc_irq_trigger==0){
|
||||
g_nfc_irq_trigger = 1;
|
||||
ql_rtos_semaphore_release(nfc_irq_sem);
|
||||
@@ -43,123 +379,109 @@ static void nfc_irq_wake(void *args)
|
||||
}
|
||||
|
||||
|
||||
void ql_nfc_task_thread(void *arvg)
|
||||
static void nfc_reader_prepare(void)
|
||||
{
|
||||
s8 read_status;
|
||||
|
||||
while(1)
|
||||
{
|
||||
// ========== 状态1: 低功耗等待中断 ==========
|
||||
if (g_nfc_state == NFC_STATE_LPCD_WAIT) {
|
||||
|
||||
DP1312EA_LPCD_Start(); // 进入低功耗检卡,等待中断
|
||||
|
||||
printf("NFC enter LPCD mode, waiting for IRQ...\r\n");
|
||||
|
||||
// 阻塞等待中断唤醒
|
||||
ql_rtos_semaphore_wait(nfc_irq_sem, 0xFFFFFFFF);
|
||||
|
||||
DP1312EA_CLEAR_IRQ();
|
||||
ql_rtos_task_sleep_ms(10); // 防抖延迟
|
||||
|
||||
printf("NFC IRQ wake up, switch to polling mode\r\n");
|
||||
|
||||
// 切换到轮询模式,重置失败计数
|
||||
g_nfc_state = NFC_STATE_POLLING;
|
||||
DP1312EA_Reset();
|
||||
Check_Chip();
|
||||
g_nfc_state = NFC_STATE_LPCD_WAIT;
|
||||
g_poll_fail_count = 0;
|
||||
|
||||
// 立即执行第一次读卡(不等待200ms)
|
||||
continue;
|
||||
g_nfc_irq_trigger = 0;
|
||||
}
|
||||
|
||||
// ========== 状态2: 200ms 轮询读卡 ==========
|
||||
if (g_nfc_state == NFC_STATE_POLLING) {
|
||||
|
||||
// 检查是否允许读卡(业务条件)
|
||||
static void nfc_reader_task_step(void)
|
||||
{
|
||||
s8 read_status;
|
||||
int can_read = 0;
|
||||
|
||||
if (g_nfc_state == NFC_STATE_LPCD_WAIT) {
|
||||
DP1312EA_LPCD_Start();
|
||||
printf("NFC enter LPCD mode, waiting for IRQ...\r\n");
|
||||
ql_rtos_semaphore_wait(nfc_irq_sem, 0xFFFFFFFF);
|
||||
|
||||
if (g_nfc_tx_mode_requested) {
|
||||
return;
|
||||
}
|
||||
|
||||
DP1312EA_CLEAR_IRQ();
|
||||
ql_rtos_task_sleep_ms(10);
|
||||
printf("NFC IRQ wake up, switch to polling mode\r\n");
|
||||
g_nfc_state = NFC_STATE_POLLING;
|
||||
g_poll_fail_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fota_start == 0 &&
|
||||
get_sleep_mode() != 1 &&
|
||||
get_mqtt_connect_status() == 1) {
|
||||
|
||||
if ((get_mqtt_connect_status() == 1 &&
|
||||
vad_audio_is_recording() != 1 &&
|
||||
if ((vad_audio_is_recording() != 1 &&
|
||||
audio_packet_type == 0 &&
|
||||
tts_playing == 0 &&
|
||||
xiaoice_get_play_counter() == 0) ||
|
||||
g_testmode == 1) {
|
||||
|
||||
can_read = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_read) {
|
||||
// 当前业务状态不允许读卡,继续等待下次轮询
|
||||
// printf("NFC business condition not met, keep polling\r\n");
|
||||
ql_rtos_task_sleep_ms(300);
|
||||
return;
|
||||
}
|
||||
|
||||
read_status = Test_NTAG213();
|
||||
if (read_status == 0) {
|
||||
printf("NFC read OK, back to LPCD\r\n");
|
||||
g_nfc_state = NFC_STATE_LPCD_WAIT;
|
||||
g_poll_fail_count = 0;
|
||||
g_nfc_irq_trigger = 0;
|
||||
ql_rtos_task_sleep_ms(10);
|
||||
return;
|
||||
}
|
||||
|
||||
g_poll_fail_count++;
|
||||
printf("NFC read failed, fail_count=%d\r\n", g_poll_fail_count);
|
||||
if (g_poll_fail_count >= 3) {
|
||||
printf("NFC fail 3 times, card removed, back to LPCD\r\n");
|
||||
g_nfc_state = NFC_STATE_LPCD_WAIT;
|
||||
g_poll_fail_count = 0;
|
||||
g_nfc_irq_trigger = 0;
|
||||
ql_rtos_task_sleep_ms(10);
|
||||
} else {
|
||||
ql_rtos_task_sleep_ms(300);
|
||||
}
|
||||
}
|
||||
|
||||
void ql_nfc_task_thread(void *arvg)
|
||||
{
|
||||
printf("NFC work mode: READER (5 clicks enable Type 2 TX)\r\n");
|
||||
|
||||
while (1) {
|
||||
if (nfc_tx_mode_is_active()) {
|
||||
printf("NFC TX mode enabled\r\n");
|
||||
DP1312EA_Reset();
|
||||
Check_Chip();
|
||||
nfc_card_emulation_task();
|
||||
nfc_reader_prepare();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 执行读卡
|
||||
// printf("NFC polling read, fail_count=%d\r\n", g_poll_fail_count);
|
||||
read_status = Test_NTAG213();
|
||||
|
||||
if (read_status == 0) {
|
||||
// ===== 读卡成功 =====
|
||||
printf("NFC read OK, continue polling\r\n");
|
||||
|
||||
// 直接回到 LPCD,不再轮询
|
||||
g_nfc_state = NFC_STATE_LPCD_WAIT;
|
||||
g_poll_fail_count = 0;
|
||||
g_nfc_irq_trigger = 0;
|
||||
|
||||
ql_rtos_task_sleep_ms(10);
|
||||
continue; // 回到 LPCD 等待
|
||||
} else {
|
||||
// ===== 读卡失败 =====
|
||||
g_poll_fail_count++;
|
||||
printf("NFC read failed, fail_count=%d\r\n", g_poll_fail_count);
|
||||
|
||||
if (g_poll_fail_count >= 3) {
|
||||
// 连续失败 3 次,认为卡已离开
|
||||
printf("NFC fail 3 times, card removed, back to LPCD\r\n");
|
||||
|
||||
// 清理状态,回到低功耗
|
||||
g_nfc_state = NFC_STATE_LPCD_WAIT;
|
||||
g_poll_fail_count = 0;
|
||||
g_nfc_irq_trigger = 0;
|
||||
// 短暂延迟确保硬件稳定
|
||||
ql_rtos_task_sleep_ms(10);
|
||||
|
||||
} else {
|
||||
// 继续轮询,可能卡只是位置偏移
|
||||
ql_rtos_task_sleep_ms(300);
|
||||
}
|
||||
}
|
||||
}
|
||||
nfc_reader_task_step();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DP1312EA_Init(void)
|
||||
{
|
||||
// 创建信号量
|
||||
ql_rtos_semaphore_create(&nfc_irq_sem, 0);
|
||||
|
||||
// 配置中断引脚
|
||||
ql_gpio_init(DP1312EA_PIN_IRQ, PIN_DIRECTION_IN, PIN_PULL_PD, PIN_LEVEL_LOW);
|
||||
ql_eint_register(DP1312EA_PIN_IRQ, PIN_RISING_EDGE, PIN_PULL_PD, NULL, nfc_irq_wake);
|
||||
ql_eint_enable_wakeup(DP1312EA_PIN_IRQ, PIN_RISING_EDGE);
|
||||
|
||||
// I2C 和芯片初始化
|
||||
DP1312EA_Reset();
|
||||
Check_Chip();
|
||||
|
||||
// 初始状态为低功耗等待
|
||||
g_nfc_state = NFC_STATE_LPCD_WAIT;
|
||||
g_poll_fail_count = 0;
|
||||
|
||||
// 创建任务
|
||||
ql_task_t nfcTaskRef = NULL;
|
||||
ql_rtos_task_create(&nfcTaskRef, 1024*6, 100, "nfc_Task_Ref", ql_nfc_task_thread, NULL);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
// 在文件顶部添加状态变量
|
||||
static uint8_t speech_detect_count = 0; // 连续语音帧计数器
|
||||
#define SPEECH_DETECT_THRESHOLD 20 // 连续检测到语音的阈值
|
||||
#define PLAYBACK_VAD_GUARD_MS 800 // 屏蔽播放结束后的尾音和音频通路切换噪声
|
||||
#define VAD_FRAME_BYTES 640 // 20ms @ 16kHz, 16-bit mono
|
||||
#define VAD_PREROLL_FRAMES 25 // 保留500ms预录音,避免吞掉用户首字
|
||||
uint8_t no_speech_timeout = 0;
|
||||
extern int g_mic_working;
|
||||
|
||||
@@ -57,6 +60,9 @@ static int64_t no_sound_start_time = 0;
|
||||
static int64_t cmd_wait_start_time = 0;
|
||||
static bool first_sound_detected = false;
|
||||
static int64_t recording_start_time = 0; // 记录录音开始时间(首次检测到声音时)
|
||||
static uint8_t vad_preroll[VAD_PREROLL_FRAMES][VAD_FRAME_BYTES];
|
||||
static size_t vad_preroll_next = 0;
|
||||
static size_t vad_preroll_count = 0;
|
||||
|
||||
static int vad_tts_playback_finished_status = 0;
|
||||
// 统计
|
||||
@@ -101,6 +107,9 @@ void set_state(system_state_t new_state) {
|
||||
no_sound_start_time = now;
|
||||
first_sound_detected = false;
|
||||
speech_frame_count = 0;
|
||||
speech_detect_count = 0;
|
||||
vad_preroll_next = 0;
|
||||
vad_preroll_count = 0;
|
||||
g_mic_working = 0;
|
||||
break;
|
||||
case STATE_RECORDING_ACTIVE:
|
||||
@@ -290,17 +299,55 @@ void vad_leave_message(void)
|
||||
audio_packet_type = 4;
|
||||
|
||||
vad_leave_message_flag = 1;
|
||||
/* A previous conversation may still be parked in WAITING_COMMAND. NFC
|
||||
* leave-message recording must enter IDLE first, otherwise g_triggered is
|
||||
* left pending forever and its 10-second no-speech timer never starts. */
|
||||
set_state(STATE_IDLE);
|
||||
vad_audio_trigger_record();
|
||||
}
|
||||
|
||||
int vad_leave_message_is_active(void)
|
||||
{
|
||||
return vad_leave_message_flag != 0 || audio_packet_type == 4;
|
||||
}
|
||||
|
||||
/* The conversation key has priority over an unfinished NFC leave-message
|
||||
* transaction. Clear every local latch before the normal conversation prompt
|
||||
* is queued so that its recording can never inherit packetType=4. */
|
||||
int vad_cancel_leave_message(void)
|
||||
{
|
||||
int was_active = vad_leave_message_is_active();
|
||||
|
||||
if (!was_active) return 0;
|
||||
|
||||
printf("cancel pending leave message\r\n");
|
||||
vad_leave_message_flag = 0;
|
||||
g_triggered = false;
|
||||
audio_packet_type = 0;
|
||||
no_speech_timeout = 0;
|
||||
speech_detect_count = 0;
|
||||
vad_preroll_next = 0;
|
||||
vad_preroll_count = 0;
|
||||
key_start_leave = 0;
|
||||
|
||||
if (vad_audio_is_recording()) {
|
||||
set_state(STATE_IDLE);
|
||||
vad_audio_stop_no_wait_resp();
|
||||
} else {
|
||||
set_state(STATE_IDLE);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 主处理函数 - 需要在循环中定期调用
|
||||
void vad_audio_process(void) {
|
||||
size_t bytes_read = 0;
|
||||
int64_t now = hal_os_up_time_ms();
|
||||
|
||||
static size_t buffer_offset = 0;
|
||||
uint8_t mic_buf[640]={0};
|
||||
int record_len = 640;
|
||||
uint8_t mic_buf[VAD_FRAME_BYTES]={0};
|
||||
int record_len = VAD_FRAME_BYTES;
|
||||
uint16 frame[320];
|
||||
int index = 0, i = 0;
|
||||
|
||||
@@ -308,7 +355,6 @@ void vad_audio_process(void) {
|
||||
switch (g_state) {
|
||||
case STATE_IDLE:
|
||||
no_speech_timeout = 0;
|
||||
vad_leave_message_flag = 0;
|
||||
speech_detect_count = 0; // 空闲时清零计数器
|
||||
vad_tts_playback_finished_status = 0;
|
||||
|
||||
@@ -326,15 +372,19 @@ void vad_audio_process(void) {
|
||||
//每次读取640字节音频数据
|
||||
ql_audio_record_read(record_hd, mic_buf, &record_len);
|
||||
|
||||
//如果检测到声音再去传输
|
||||
// if(first_sound_detected){
|
||||
//实际上每次编码需要输入1152字节的PCM数据
|
||||
if (g_state == STATE_RECORDING_WAIT_SOUND) {
|
||||
/* 首次开口确认前不向服务端发送静音。只在本地保留最近500ms,
|
||||
* 确认开口时再回填,防止截掉用户首字。 */
|
||||
memcpy(vad_preroll[vad_preroll_next], mic_buf, VAD_FRAME_BYTES);
|
||||
vad_preroll_next = (vad_preroll_next + 1) % VAD_PREROLL_FRAMES;
|
||||
if (vad_preroll_count < VAD_PREROLL_FRAMES) {
|
||||
vad_preroll_count++;
|
||||
}
|
||||
} else {
|
||||
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){
|
||||
|
||||
//quec_usbcdc_write((uint8*)mic_buf, record_len);
|
||||
ql_rtos_semaphore_release(sender_sem); //告诉线程去获取录音数据
|
||||
ql_rtos_semaphore_release(sender_sem);
|
||||
}
|
||||
}
|
||||
// }
|
||||
#if 1
|
||||
for(index=0; index<320; index++)
|
||||
{
|
||||
@@ -347,6 +397,12 @@ void vad_audio_process(void) {
|
||||
|
||||
// 首次声音检测
|
||||
if (!first_sound_detected) {
|
||||
/* 播放刚结束时扬声器尾音和PA切换噪声容易被FVAD认为用户开口。 */
|
||||
if ((now - no_sound_start_time) < PLAYBACK_VAD_GUARD_MS) {
|
||||
speech_detect_count = 0;
|
||||
buffer_offset = 0;
|
||||
break;
|
||||
}
|
||||
if (current_vad == VAD_STATE_ACTIVE) {
|
||||
speech_detect_count++;
|
||||
printf("Speech frame count: %d\r\n", speech_detect_count);
|
||||
@@ -364,6 +420,21 @@ void vad_audio_process(void) {
|
||||
#endif
|
||||
|
||||
first_sound_detected = true;
|
||||
{
|
||||
size_t first = (vad_preroll_next + VAD_PREROLL_FRAMES - vad_preroll_count) % VAD_PREROLL_FRAMES;
|
||||
size_t n;
|
||||
int preroll_queued = 0;
|
||||
for (n = 0; n < vad_preroll_count; n++) {
|
||||
size_t frame_index = (first + n) % VAD_PREROLL_FRAMES;
|
||||
if (hal_os_rb_write(ring_sender, vad_preroll[frame_index], VAD_FRAME_BYTES, 25) > 0) {
|
||||
preroll_queued = 1;
|
||||
}
|
||||
}
|
||||
if (preroll_queued) {
|
||||
ql_rtos_semaphore_release(sender_sem);
|
||||
}
|
||||
vad_preroll_count = 0;
|
||||
}
|
||||
set_state(STATE_RECORDING_ACTIVE);
|
||||
printf("First speech confirmed by FVAD (continuous %d frames)", SPEECH_DETECT_THRESHOLD);
|
||||
}
|
||||
@@ -420,12 +491,17 @@ void vad_audio_process(void) {
|
||||
|
||||
case STATE_WAITING_COMMAND:
|
||||
if(vad_tts_playback_finished_status == 1) {
|
||||
int leave_message_finished = vad_leave_message_flag != 0;
|
||||
printf("finished_status:%d, no_speech_timeout:%d\n", vad_tts_playback_finished_status, no_speech_timeout);
|
||||
set_state(STATE_IDLE);
|
||||
ql_rtos_task_sleep_ms(50);
|
||||
if(no_speech_timeout == 0&&vad_leave_message_flag==0) {
|
||||
vad_audio_trigger_record();
|
||||
}
|
||||
if(leave_message_finished) {
|
||||
vad_leave_message_flag = 0;
|
||||
audio_packet_type = 0;
|
||||
}
|
||||
}
|
||||
|
||||
key_start_leave = 0;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ql_ping.h"
|
||||
#include "ql_nw.h"
|
||||
#include "ql_atcmd.h"
|
||||
#include "ql_platform.h"
|
||||
#include "layer3.h"
|
||||
#include "ql_uart.h"
|
||||
#include "hal_api.h"
|
||||
@@ -29,6 +30,9 @@
|
||||
|
||||
|
||||
#define PCM_FRAME_LENGTH 1152
|
||||
#define CLOUD_TTS_ERROR_URL_FORMAT \
|
||||
"http://101.35.224.118:8080/assets/roles/banban/tts_error/tts_error%02u.mp3"
|
||||
#define LOCAL_TTS_ERROR_PATH "U:/tts_error.mp3"
|
||||
|
||||
static int g_sim_ready = 0;
|
||||
|
||||
@@ -50,7 +54,10 @@ static int onmessage_status = -1;
|
||||
volatile int tts_playing = 0;
|
||||
unsigned char wss_connect_start = 0;
|
||||
static volatile int player_mq_couter = 0;
|
||||
static unsigned char audio_in_task_cancel = 0;
|
||||
/* Set while a barge-in prompt bridges the old and new sessions. The old
|
||||
* server response can still deliver URLs after its player queue was cleared. */
|
||||
static volatile int barge_in_transition = 0;
|
||||
static volatile unsigned char audio_in_task_cancel = 0;
|
||||
|
||||
static unsigned char xiaoice_wss_connect_status = 0;
|
||||
static unsigned char wss_connect_counter = 0;
|
||||
@@ -63,13 +70,20 @@ extern int mqtt_task_run_flag;
|
||||
static unsigned char shine_init = 0;
|
||||
extern int fisrt_net_start;
|
||||
extern u8 power_down_status;
|
||||
extern int aofei_mp3_player_get_result(void);
|
||||
extern int vad_leave_message_is_active(void);
|
||||
extern int vad_cancel_leave_message(void);
|
||||
|
||||
void sendAuthentication(void);
|
||||
void vad_start_func(void);
|
||||
void xiaoice_close_all_audio(void);
|
||||
|
||||
int audio_packet_type = 0;
|
||||
|
||||
|
||||
typedef struct ice_work_item_s
|
||||
{
|
||||
int type; //0-tts_out 1-session_continue 2-session_end 3-error
|
||||
int type; //0-tts_out 1-session_continue 2-session_end 3-云端超时提示(失败时播放本地兜底)
|
||||
int error;
|
||||
int del_url;
|
||||
void *data;
|
||||
@@ -192,6 +206,67 @@ static void remove_underscores(char *str) {
|
||||
*dst = '\0'; // 添加字符串结束符
|
||||
}
|
||||
|
||||
/* 生成 banban 角色的随机云端对话超时提示音 URL。 */
|
||||
static int build_random_tts_error_url(char *url, size_t url_size)
|
||||
{
|
||||
static uint32_t random_sequence = 0;
|
||||
static unsigned int last_random_index = 10;
|
||||
uint32_t random_value;
|
||||
unsigned int random_index;
|
||||
int written;
|
||||
|
||||
if(url == NULL || url_size == 0){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 某些设备上硬件随机接口可能持续返回 0,因此混合伪随机数、系统 tick
|
||||
* 和递增序列;若仍与上一次相同,则向后偏移,避免连续重复播放。
|
||||
*/
|
||||
random_sequence++;
|
||||
random_value = hal_os_get_rand();
|
||||
random_value ^= ql_pseudo_random_number();
|
||||
random_value ^= ql_rtos_get_systicks();
|
||||
random_value ^= random_sequence * 0x9E3779B9U;
|
||||
random_index = random_value % 10;
|
||||
if(random_index == last_random_index){
|
||||
random_index = (random_index + 1 + (random_sequence % 9)) % 10;
|
||||
}
|
||||
last_random_index = random_index;
|
||||
|
||||
written = snprintf(url, url_size, CLOUD_TTS_ERROR_URL_FORMAT, random_index);
|
||||
if(written < 0 || (size_t)written >= url_size){
|
||||
printf("random tts error url buffer too small\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("random cloud tts error index:%02u, url:%s\n", random_index, url);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 后台下发任意角色的对话超时 URL 时,统一替换成 banban 的随机云端资源。
|
||||
* 非对话超时 URL 保持不变。
|
||||
*/
|
||||
static int randomize_tts_error_url(char *url, size_t url_size)
|
||||
{
|
||||
char *file_name;
|
||||
|
||||
if(url == NULL || url_size == 0){
|
||||
return 0;
|
||||
}
|
||||
|
||||
file_name = strrchr(url, '/');
|
||||
file_name = (file_name == NULL) ? url : file_name + 1;
|
||||
|
||||
if(strncmp(file_name, "tts_error", strlen("tts_error")) != 0 &&
|
||||
strncmp(file_name, "tts_err", strlen("tts_err")) != 0){
|
||||
return 0;
|
||||
}
|
||||
|
||||
return build_random_tts_error_url(url, url_size);
|
||||
}
|
||||
|
||||
void add_file_to_play_player_queue(char *path, int type)
|
||||
{
|
||||
ice_work_item *item = NULL;
|
||||
@@ -246,6 +321,14 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
|
||||
return;
|
||||
}
|
||||
|
||||
/* The new session id is generated only when recording starts. Until
|
||||
* then, every TTS message on this socket belongs to the interrupted
|
||||
* session and must not be allowed to refill the cleared player queue. */
|
||||
if (barge_in_transition) {
|
||||
printf("drop stale websocket message during barge-in\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("message(txt), %d(byte): %s ", len, text);
|
||||
|
||||
char url_buff[128]={0x00};
|
||||
@@ -266,14 +349,41 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
|
||||
|
||||
}else{//TTS_URL:audio_url
|
||||
wss_connect_start = 0;
|
||||
char *ret_str1= strstr(text, "http:");
|
||||
char *ret_str2= strstr(text, ".mp3");
|
||||
int len = 0;
|
||||
char *ret_str1 = strstr(text, "https://");
|
||||
char *ret_str2 = NULL;
|
||||
size_t url_len = 0;
|
||||
int is_tts_error_url = 0;
|
||||
int is_message_ok_url = 0;
|
||||
if(ret_str1 == NULL){
|
||||
ret_str1 = strstr(text, "http://");
|
||||
}
|
||||
if(ret_str1 != NULL){
|
||||
ret_str2 = strstr(ret_str1, ".mp3");
|
||||
}
|
||||
if(ret_str1!=NULL && ret_str2!=NULL){
|
||||
|
||||
len = strlen(ret_str1)-strlen(ret_str2);
|
||||
strncpy(url_buff, ret_str1, len);
|
||||
sprintf(url_buff+len, "%s", ".mp3");
|
||||
/* 提取完整的 HTTP/HTTPS MP3 URL,结尾额外包含“.mp3”四个字符 */
|
||||
url_len = (size_t)(ret_str2 - ret_str1) + strlen(".mp3");
|
||||
if(url_len >= sizeof(url_buff)){
|
||||
printf("audio url too long:%d\n", (int)url_len);
|
||||
return;
|
||||
}
|
||||
memcpy(url_buff, ret_str1, url_len);
|
||||
url_buff[url_len] = '\0';
|
||||
is_message_ok_url = strstr(url_buff, "message_ok") != NULL;
|
||||
|
||||
/* A cancelled NFC transaction may still have a response in flight.
|
||||
* Never let that acknowledgement enter a normal conversation. */
|
||||
if(is_message_ok_url && !vad_leave_message_is_active()){
|
||||
printf("drop stale leave message ack\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 两个角色统一使用 banban 的 HTTP 随机超时提示音 */
|
||||
is_tts_error_url = randomize_tts_error_url(url_buff, sizeof(url_buff));
|
||||
if(is_tts_error_url < 0){
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(url_buff_bak, url_buff, strlen(url_buff)+1);
|
||||
|
||||
@@ -292,7 +402,7 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
|
||||
tts_play_status = 2;
|
||||
}
|
||||
|
||||
if(strstr(url_buff, "tts_error")!=NULL || strstr(url_buff, "message_ok")!=NULL){
|
||||
if(is_tts_error_url == 1 || is_message_ok_url){
|
||||
printf("tts_error or message_ok\n");
|
||||
vad_set_speech_timeout_flag(1);
|
||||
}
|
||||
@@ -301,7 +411,8 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
|
||||
if(tts_playing==2) return;
|
||||
|
||||
|
||||
add_file_to_play_player_queue(url_buff, 0);
|
||||
/* 对话超时提示使用 type=3,云端播放失败时由播放线程切换到本地兜底。 */
|
||||
add_file_to_play_player_queue(url_buff, is_tts_error_url == 1 ? 3 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,6 +785,9 @@ void generateSessionId(void)
|
||||
#if 1
|
||||
void sendStartPacket(void) {
|
||||
generateSessionId();
|
||||
/* sessionId now identifies the new conversation. Late URLs from the old
|
||||
* conversation will fail the existing session-id check below. */
|
||||
barge_in_transition = 0;
|
||||
generateDeviceId();
|
||||
gSequenceNumber = 0;
|
||||
|
||||
@@ -794,13 +908,19 @@ extern const char *mp3_file[];
|
||||
int app_play_tips_file(int index)
|
||||
{
|
||||
int file_valid_check = 0;
|
||||
int access_ret = 0;
|
||||
|
||||
printf("xiaoice_play_index:%d\n", index);
|
||||
|
||||
//正在录音过程中不允许播放
|
||||
// if(xiaoice_get_tts_play_status() || get_start_mic_flag()==1) return -1;
|
||||
if(power_down_status!=1){
|
||||
if(vad_audio_is_recording() || tts_playing!=0 || fota_start==1 || power_down_status==2 || get_sleep_mode()==1) return -1;
|
||||
if(vad_audio_is_recording() || tts_playing!=0 || fota_start==1 || power_down_status==2 || get_sleep_mode()==1) {
|
||||
printf("tips blocked:index=%d vad=%d tts=%d fota=%d power=%d sleep=%d\r\n",
|
||||
index, vad_audio_is_recording(), tts_playing, fota_start,
|
||||
power_down_status, get_sleep_mode());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
char filename_buff[64]={0x00};
|
||||
@@ -810,15 +930,18 @@ int app_play_tips_file(int index)
|
||||
if(index==0) {
|
||||
file_valid_check = 0;
|
||||
}else{
|
||||
if(ql_access(filename_buff, 0)==0){
|
||||
access_ret = ql_access(filename_buff, 0);
|
||||
if(access_ret==0){
|
||||
file_valid_check = 1;
|
||||
}else{
|
||||
printf("tips file missing:index=%d path=%s access=%d\r\n",
|
||||
index, filename_buff, access_ret);
|
||||
file_valid_check = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(file_valid_check!=2){
|
||||
if(index==18){
|
||||
if(index==18 || index==19){
|
||||
add_file_to_play_player_queue(filename_buff, 2);
|
||||
}else{
|
||||
add_file_to_play_player_queue(filename_buff, 0);
|
||||
@@ -876,6 +999,7 @@ int app_play_ota_tips_file(void)
|
||||
void app_player_thread(void *arvg)
|
||||
{
|
||||
ice_work_item *player_item=NULL;
|
||||
int play_ret;
|
||||
|
||||
while(1){
|
||||
ql_rtos_queue_wait(player_queue, (u8*)(&player_item), sizeof(ice_work_item *), 0xFFFFFFFF);
|
||||
@@ -887,9 +1011,26 @@ void app_player_thread(void *arvg)
|
||||
|
||||
app_set_app_operation(1);
|
||||
aofei_mp3_player(player_item->data);
|
||||
play_ret = aofei_mp3_player_get_result();
|
||||
app_set_app_operation(1);
|
||||
|
||||
if(player_mq_couter>0) player_mq_couter--;
|
||||
|
||||
/*
|
||||
* type=3 只用于对话超时提示。云端连接、HTTP、读取或播放器初始化
|
||||
* 失败时才排入本地兜底;如果用户已经开始下一轮录音,则丢弃旧提示。
|
||||
*/
|
||||
if(play_ret != 0 && player_item->type == 3 &&
|
||||
!vad_audio_is_recording() && tts_playing_ret() != 2){
|
||||
printf("cloud tts error play failed:%d, fallback:%s\r\n",
|
||||
play_ret, LOCAL_TTS_ERROR_PATH);
|
||||
if(ql_access(LOCAL_TTS_ERROR_PATH, 0) == 0){
|
||||
add_file_to_play_player_queue(LOCAL_TTS_ERROR_PATH, 0);
|
||||
}else{
|
||||
printf("local tts error fallback missing:%s\r\n", LOCAL_TTS_ERROR_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
if(player_mq_couter==0){
|
||||
printf("player_mq_couter==0\r\n");
|
||||
|
||||
@@ -930,26 +1071,69 @@ void app_player_thread(void *arvg)
|
||||
|
||||
int tts_play_interrupt_func(void)
|
||||
{
|
||||
int playback_interrupted = 0;
|
||||
int leave_message_cancelled = 0;
|
||||
|
||||
/* A short press explicitly starts a normal conversation. Cancel an NFC
|
||||
* leave-message before the generic "already recording" guard, otherwise a
|
||||
* pending/recording leave-message can leak packetType=4 into this session. */
|
||||
leave_message_cancelled = vad_cancel_leave_message();
|
||||
if(leave_message_cancelled){
|
||||
uint64_t cancel_start;
|
||||
|
||||
/* Let the sender task discard buffered PCM and its encoder without
|
||||
* emitting END. Only then close the old leave-message socket. */
|
||||
audio_in_task_cancel = 1;
|
||||
ql_rtos_semaphore_release(sender_sem);
|
||||
cancel_start = hal_os_up_time_ms();
|
||||
while(audio_in_task_cancel &&
|
||||
hal_os_up_time_ms() - cancel_start < OTA_AUDIO_STOP_TIMEOUT_MS){
|
||||
ql_rtos_task_sleep_ms(20);
|
||||
}
|
||||
if(audio_in_task_cancel){
|
||||
printf("wait leave message sender cancel timeout\r\n");
|
||||
}
|
||||
talkingq_ws_disconnect();
|
||||
}
|
||||
|
||||
//如果在vad状态,则直接返回
|
||||
if(vad_audio_is_recording()||g_mic_working) return -1;
|
||||
|
||||
g_mic_working = 1;//等待转为vad状态就清0 或者拾音超时 或者ws没有正常
|
||||
|
||||
/* Remember whether this key press is interrupting TTS. tts_playing == 2
|
||||
* is only a cancellation signal for the old player; it must not leak into
|
||||
* the next conversation, otherwise prompts and error audio are discarded. */
|
||||
playback_interrupted = (tts_playing != 0 || player_mq_couter > 0);
|
||||
barge_in_transition = playback_interrupted;
|
||||
xiaoice_close_all_audio();
|
||||
|
||||
talkingq_ws_connect();
|
||||
|
||||
if(get_xiaoice_connected_status()!=2) {
|
||||
/* Let the caller play its local network-error prompt. */
|
||||
tts_play_status = 0;
|
||||
tts_playing = 0;
|
||||
barge_in_transition = 0;
|
||||
g_mic_working = 0;
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(tts_playing==2){
|
||||
printf("vad_start_func0\n");
|
||||
vad_start_func();//如果是打断就不播报
|
||||
if(playback_interrupted){
|
||||
printf("barge_in_prompt\n");
|
||||
/* The old player has finished consuming the cancellation state. */
|
||||
tts_play_status = 0;
|
||||
tts_playing = 0;
|
||||
/* Use the same local-prompt flow as ai_active_zh.mp3. Queue type 2
|
||||
* starts VAD after playback; fall back to VAD if the file is absent. */
|
||||
if(app_play_tips_file(19) != 0){
|
||||
vad_start_func();
|
||||
}
|
||||
}else{
|
||||
app_play_tips_file(18);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void stop_player_func(void)
|
||||
@@ -957,6 +1141,7 @@ void stop_player_func(void)
|
||||
vad_audio_stop_record();
|
||||
xiaoice_close_all_audio();
|
||||
|
||||
tts_play_status = 0;
|
||||
tts_playing = 0;
|
||||
}
|
||||
|
||||
@@ -1076,9 +1261,19 @@ static void check_connect_thread_entry(void *parameter)
|
||||
printf("tts_error_set_speech1\n");
|
||||
vad_set_speech_timeout_flag(1);
|
||||
|
||||
/*
|
||||
* 后台没有返回任何音频 URL 时,也先由设备生成随机云端提示音。
|
||||
* type=3 会在 HTTP 下载或播放初始化失败时自动切换到本地音频。
|
||||
*/
|
||||
{
|
||||
char cloud_tts_error_url[128] = {0};
|
||||
if(build_random_tts_error_url(cloud_tts_error_url,
|
||||
sizeof(cloud_tts_error_url)) == 1){
|
||||
add_file_to_play_player_queue(cloud_tts_error_url, 3);
|
||||
}else{
|
||||
app_play_tips_file(9);
|
||||
//talkingq_ws_send_text("REQUEST_PROMPT_SOUND:tts_error");
|
||||
//改为播放本地音频 听不清 请再说一遍
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1123,9 +1318,16 @@ void xiaoice_close_all_audio(void)
|
||||
|
||||
timeout = hal_os_up_time_ms();
|
||||
|
||||
while(tts_play_status&&tts_playing!=2){
|
||||
/* The queue counter also includes the item currently owned by the
|
||||
* player task. Wait for that item to observe tts_playing == 2 and
|
||||
* finish cleanup before the cancellation state is reused/reset. */
|
||||
while(player_mq_couter>0){
|
||||
ql_rtos_task_sleep_ms(20);
|
||||
if(hal_os_up_time_ms()-timeout>10000) break;
|
||||
if(hal_os_up_time_ms()-timeout>OTA_AUDIO_STOP_TIMEOUT_MS){
|
||||
printf("wait player stop timeout, counter:%d\r\n",
|
||||
player_mq_couter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1145,9 +1347,34 @@ void app_sender_thread(void *arvg)
|
||||
ql_rtos_semaphore_wait(sender_sem, 0xFFFFFFFF);
|
||||
|
||||
sender_loop:
|
||||
if(audio_in_task_cancel){
|
||||
left = rb_get_space_used(ring_sender);
|
||||
while(left > 0){
|
||||
int discard_len = left > PCM_FRAME_LENGTH ? PCM_FRAME_LENGTH : left;
|
||||
hal_os_rb_read(ring_sender, sender_buf, discard_len, 0);
|
||||
left = rb_get_space_used(ring_sender);
|
||||
}
|
||||
if(shine_init != 0){
|
||||
shine_close(sh_shine);
|
||||
sh_shine = NULL;
|
||||
shine_init = 0;
|
||||
}
|
||||
audio_packet_type = 0;
|
||||
audio_in_task_cancel = 0;
|
||||
printf("leave message sender cancelled\r\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!app_get_data_call_net_status()) continue;
|
||||
|
||||
/* One recording can release the semaphore many times while the sender
|
||||
* drains the ring buffer. A stale token must never create an empty
|
||||
* session after the previous encoder has already been closed. */
|
||||
left = rb_get_space_used(ring_sender);
|
||||
if(shine_init == 0 && left <= 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(shine_init==0){
|
||||
shine_set_config_mpeg_defaults(&(sh_config.mpeg)); //--init sh_config struct
|
||||
sh_config.wave.channels=PCM_MONO; //PCM_STEREO;//PCM_MONO;
|
||||
@@ -1201,6 +1428,9 @@ sender_loop:
|
||||
shine_init = 0;
|
||||
|
||||
audio_packet_type = 0;
|
||||
/* Discard semaphore tokens already represented by the PCM that this
|
||||
* session drained. Recording has stopped, so no new frames race here. */
|
||||
while(ql_rtos_semaphore_wait(sender_sem, 0) == 0) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user