多模态
This commit is contained in:
@@ -21,6 +21,8 @@
|
||||
#define PRINTER_NOTIFY_UUID16 0xFFF1
|
||||
#define PRINTER_WRITE_UUID16 0xFFF2
|
||||
#define CCCD_UUID16 0x2902
|
||||
#define PRINTER_NAME_PRIMARY "TQPrinter"
|
||||
#define PRINTER_NAME_LEGACY "lyfPrinter"
|
||||
|
||||
#define EVT_CONNECTED BIT0
|
||||
#define EVT_READY BIT1
|
||||
@@ -35,7 +37,7 @@ static ble_frame_rx_cb_t s_rx_cb;
|
||||
|
||||
static uint8_t s_addr_type;
|
||||
static ble_addr_t s_target_addr;
|
||||
static char s_target_name[32] = "lyfPrinter";
|
||||
static char s_target_name[32] = PRINTER_NAME_PRIMARY;
|
||||
static bool s_match_any_compatible;
|
||||
|
||||
static uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
|
||||
@@ -117,6 +119,21 @@ static bool normalized_name_match(const char *adv_name, const char *target_name)
|
||||
return (strstr(adv, target) != NULL) || (strstr(target, adv) != NULL);
|
||||
}
|
||||
|
||||
static bool is_known_printer_name(const char *name) {
|
||||
return normalized_name_match(name, PRINTER_NAME_PRIMARY) ||
|
||||
normalized_name_match(name, PRINTER_NAME_LEGACY);
|
||||
}
|
||||
|
||||
static bool target_name_match_with_alias(const char *adv_name, const char *target_name) {
|
||||
if (normalized_name_match(adv_name, target_name)) {
|
||||
return true;
|
||||
}
|
||||
if (!is_known_printer_name(target_name)) {
|
||||
return false;
|
||||
}
|
||||
return is_known_printer_name(adv_name);
|
||||
}
|
||||
|
||||
static void reset_discovery_state(void) {
|
||||
s_service_start = 0;
|
||||
s_service_end = 0;
|
||||
@@ -347,7 +364,7 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) {
|
||||
|
||||
if (s_match_any_compatible) {
|
||||
bool uuid_match = adv_has_uuid16(&fields, PRINTER_SERVICE_UUID16);
|
||||
bool name_match = has_name && normalized_name_match(name, "lyfPrinter");
|
||||
bool name_match = has_name && is_known_printer_name(name);
|
||||
if (!uuid_match && !name_match) {
|
||||
return 0;
|
||||
}
|
||||
@@ -357,7 +374,7 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) {
|
||||
event->disc.rssi,
|
||||
uuid_match ? 1 : 0);
|
||||
} else {
|
||||
if (!has_name || !normalized_name_match(name, s_target_name)) {
|
||||
if (!has_name || !target_name_match_with_alias(name, s_target_name)) {
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "Found target device '%s' RSSI=%d", name, event->disc.rssi);
|
||||
|
||||
362
main/platform/src/voice_audio.c
Normal file
362
main/platform/src/voice_audio.c
Normal file
@@ -0,0 +1,362 @@
|
||||
#include "voice_audio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "audio_codec_if.h"
|
||||
#include "driver/i2s_std.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_codec_dev.h"
|
||||
#include "esp_codec_dev_defaults.h"
|
||||
#include "esp_idf_version.h"
|
||||
#include "esp_log.h"
|
||||
#include "es8311_codec.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && !defined(CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE)
|
||||
#include "driver/i2c_master.h"
|
||||
#define VOICE_USE_IDF_I2C_MASTER
|
||||
#else
|
||||
#include "driver/i2c.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "voice_audio";
|
||||
|
||||
#ifdef CONFIG_TQ_VOICE_CODEC_PA_PIN_REVERSED
|
||||
#define VOICE_CODEC_PA_REVERSED true
|
||||
#else
|
||||
#define VOICE_CODEC_PA_REVERSED false
|
||||
#endif
|
||||
|
||||
static SemaphoreHandle_t s_lock;
|
||||
|
||||
#ifdef VOICE_USE_IDF_I2C_MASTER
|
||||
static i2c_master_bus_handle_t s_i2c_bus;
|
||||
#endif
|
||||
|
||||
static i2s_chan_handle_t s_i2s_tx;
|
||||
static i2s_chan_handle_t s_i2s_rx;
|
||||
|
||||
static const audio_codec_ctrl_if_t *s_codec_ctrl_if;
|
||||
static const audio_codec_gpio_if_t *s_codec_gpio_if;
|
||||
static const audio_codec_data_if_t *s_codec_data_if;
|
||||
static const audio_codec_if_t *s_codec_if;
|
||||
static esp_codec_dev_handle_t s_codec_dev;
|
||||
|
||||
static bool s_initialized;
|
||||
static bool s_opened;
|
||||
static uint32_t s_sample_rate;
|
||||
static uint8_t s_channels;
|
||||
static uint8_t s_bits_per_sample;
|
||||
|
||||
static void fill_err(char *err, size_t err_len, const char *msg) {
|
||||
if (err != NULL && err_len > 0) {
|
||||
snprintf(err, err_len, "%s", msg != NULL ? msg : "unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t voice_audio_init_i2c(void) {
|
||||
#ifdef VOICE_USE_IDF_I2C_MASTER
|
||||
if (s_i2c_bus != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
i2c_master_bus_config_t bus_cfg = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.i2c_port = CONFIG_TQ_VOICE_I2C_PORT,
|
||||
.sda_io_num = CONFIG_TQ_VOICE_I2C_SDA_PIN,
|
||||
.scl_io_num = CONFIG_TQ_VOICE_I2C_SCL_PIN,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags.enable_internal_pullup = true,
|
||||
};
|
||||
|
||||
return i2c_new_master_bus(&bus_cfg, &s_i2c_bus);
|
||||
#else
|
||||
i2c_config_t i2c_cfg = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = CONFIG_TQ_VOICE_I2C_SDA_PIN,
|
||||
.scl_io_num = CONFIG_TQ_VOICE_I2C_SCL_PIN,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 100000,
|
||||
};
|
||||
|
||||
ESP_RETURN_ON_ERROR(i2c_param_config(CONFIG_TQ_VOICE_I2C_PORT, &i2c_cfg), TAG, "i2c_param_config failed");
|
||||
return i2c_driver_install(CONFIG_TQ_VOICE_I2C_PORT, i2c_cfg.mode, 0, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static esp_err_t voice_audio_init_i2s(uint32_t sample_rate) {
|
||||
if (s_i2s_tx != NULL && s_i2s_rx != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)CONFIG_TQ_VOICE_I2S_PORT,
|
||||
I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_desc_num = 6;
|
||||
chan_cfg.dma_frame_num = 240;
|
||||
|
||||
ESP_RETURN_ON_ERROR(i2s_new_channel(&chan_cfg, &s_i2s_tx, &s_i2s_rx), TAG, "i2s_new_channel failed");
|
||||
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate),
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {
|
||||
.mclk = CONFIG_TQ_VOICE_I2S_MCLK_PIN,
|
||||
.bclk = CONFIG_TQ_VOICE_I2S_BCLK_PIN,
|
||||
.ws = CONFIG_TQ_VOICE_I2S_WS_PIN,
|
||||
.dout = CONFIG_TQ_VOICE_I2S_DOUT_PIN,
|
||||
.din = CONFIG_TQ_VOICE_I2S_DIN_PIN,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
esp_err_t err = i2s_channel_init_std_mode(s_i2s_tx, &std_cfg);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = i2s_channel_init_std_mode(s_i2s_rx, &std_cfg);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(i2s_channel_enable(s_i2s_tx), TAG, "i2s tx enable failed");
|
||||
ESP_RETURN_ON_ERROR(i2s_channel_enable(s_i2s_rx), TAG, "i2s rx enable failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t voice_audio_build_codec_dev(void) {
|
||||
if (s_codec_dev != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
audio_codec_i2c_cfg_t i2c_cfg = {
|
||||
.port = (uint8_t)CONFIG_TQ_VOICE_I2C_PORT,
|
||||
.addr = (uint8_t)CONFIG_TQ_VOICE_CODEC_ADDR,
|
||||
#ifdef VOICE_USE_IDF_I2C_MASTER
|
||||
.bus_handle = s_i2c_bus,
|
||||
#endif
|
||||
};
|
||||
|
||||
s_codec_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
||||
if (s_codec_ctrl_if == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
audio_codec_i2s_cfg_t i2s_cfg = {
|
||||
.port = (uint8_t)CONFIG_TQ_VOICE_I2S_PORT,
|
||||
.rx_handle = s_i2s_rx,
|
||||
.tx_handle = s_i2s_tx,
|
||||
};
|
||||
|
||||
s_codec_data_if = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
if (s_codec_data_if == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
s_codec_gpio_if = audio_codec_new_gpio();
|
||||
if (s_codec_gpio_if == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
es8311_codec_cfg_t codec_cfg = {
|
||||
.ctrl_if = s_codec_ctrl_if,
|
||||
.gpio_if = s_codec_gpio_if,
|
||||
.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH,
|
||||
.pa_pin = CONFIG_TQ_VOICE_CODEC_PA_PIN,
|
||||
.pa_reverted = VOICE_CODEC_PA_REVERSED,
|
||||
.master_mode = false,
|
||||
.use_mclk = (CONFIG_TQ_VOICE_I2S_MCLK_PIN >= 0),
|
||||
.digital_mic = false,
|
||||
.invert_mclk = false,
|
||||
.invert_sclk = false,
|
||||
};
|
||||
|
||||
s_codec_if = es8311_codec_new(&codec_cfg);
|
||||
if (s_codec_if == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_codec_dev_cfg_t dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,
|
||||
.codec_if = s_codec_if,
|
||||
.data_if = s_codec_data_if,
|
||||
};
|
||||
|
||||
s_codec_dev = esp_codec_dev_new(&dev_cfg);
|
||||
if (s_codec_dev == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t voice_audio_prepare(const voice_audio_open_config_t *cfg) {
|
||||
ESP_RETURN_ON_ERROR(voice_audio_init_i2c(), TAG, "i2c init failed");
|
||||
ESP_RETURN_ON_ERROR(voice_audio_init_i2s(cfg->sample_rate), TAG, "i2s init failed");
|
||||
ESP_RETURN_ON_ERROR(voice_audio_build_codec_dev(), TAG, "codec dev init failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t voice_audio_open(const voice_audio_open_config_t *cfg, char *err, size_t err_len) {
|
||||
if (cfg == NULL || cfg->sample_rate == 0 || cfg->channels == 0 || cfg->bits_per_sample != 16) {
|
||||
fill_err(err, err_len, "invalid voice audio config");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (s_lock == NULL) {
|
||||
s_lock = xSemaphoreCreateMutex();
|
||||
if (s_lock == NULL) {
|
||||
fill_err(err, err_len, "create mutex failed");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
fill_err(err, err_len, "voice audio lock timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (s_opened) {
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t init_err = voice_audio_prepare(cfg);
|
||||
if (init_err != ESP_OK) {
|
||||
fill_err(err, err_len, "voice audio peripheral init failed");
|
||||
xSemaphoreGive(s_lock);
|
||||
return init_err;
|
||||
}
|
||||
|
||||
if (s_initialized && (s_sample_rate != cfg->sample_rate ||
|
||||
s_channels != cfg->channels ||
|
||||
s_bits_per_sample != cfg->bits_per_sample)) {
|
||||
fill_err(err, err_len, "voice audio format changed after init");
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.sample_rate = cfg->sample_rate,
|
||||
.channel = cfg->channels,
|
||||
.bits_per_sample = cfg->bits_per_sample,
|
||||
};
|
||||
|
||||
int rc = esp_codec_dev_open(s_codec_dev, &fs);
|
||||
if (rc != ESP_CODEC_DEV_OK) {
|
||||
fill_err(err, err_len, "esp_codec_dev_open failed");
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
(void)esp_codec_dev_set_out_vol(s_codec_dev, cfg->output_volume);
|
||||
(void)esp_codec_dev_set_in_gain(s_codec_dev, cfg->input_gain_db);
|
||||
(void)esp_codec_dev_set_out_mute(s_codec_dev, false);
|
||||
(void)esp_codec_dev_set_in_mute(s_codec_dev, false);
|
||||
|
||||
s_sample_rate = cfg->sample_rate;
|
||||
s_channels = cfg->channels;
|
||||
s_bits_per_sample = cfg->bits_per_sample;
|
||||
s_initialized = true;
|
||||
s_opened = true;
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"voice audio opened: sr=%u ch=%u bits=%u",
|
||||
(unsigned)s_sample_rate,
|
||||
(unsigned)s_channels,
|
||||
(unsigned)s_bits_per_sample);
|
||||
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void voice_audio_close(void) {
|
||||
if (s_lock == NULL) {
|
||||
return;
|
||||
}
|
||||
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_opened) {
|
||||
(void)esp_codec_dev_set_in_mute(s_codec_dev, true);
|
||||
(void)esp_codec_dev_set_out_mute(s_codec_dev, true);
|
||||
(void)esp_codec_dev_close(s_codec_dev);
|
||||
s_opened = false;
|
||||
}
|
||||
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
|
||||
bool voice_audio_is_open(void) {
|
||||
return s_opened;
|
||||
}
|
||||
|
||||
esp_err_t voice_audio_read_pcm(int16_t *pcm,
|
||||
size_t samples,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (pcm == NULL || samples == 0) {
|
||||
fill_err(err, err_len, "invalid read args");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (s_lock == NULL || xSemaphoreTake(s_lock, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
|
||||
fill_err(err, err_len, "voice read lock timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (!s_opened) {
|
||||
fill_err(err, err_len, "voice audio not opened");
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
int rc = esp_codec_dev_read(s_codec_dev, pcm, (int)(samples * sizeof(int16_t)));
|
||||
xSemaphoreGive(s_lock);
|
||||
if (rc != ESP_CODEC_DEV_OK) {
|
||||
fill_err(err, err_len, "codec read failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t voice_audio_write_pcm(const int16_t *pcm,
|
||||
size_t samples,
|
||||
uint32_t timeout_ms,
|
||||
char *err,
|
||||
size_t err_len) {
|
||||
if (pcm == NULL || samples == 0) {
|
||||
fill_err(err, err_len, "invalid write args");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (s_lock == NULL || xSemaphoreTake(s_lock, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
|
||||
fill_err(err, err_len, "voice write lock timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (!s_opened) {
|
||||
fill_err(err, err_len, "voice audio not opened");
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
int rc = esp_codec_dev_write(s_codec_dev, (void *)pcm, (int)(samples * sizeof(int16_t)));
|
||||
xSemaphoreGive(s_lock);
|
||||
if (rc != ESP_CODEC_DEV_OK) {
|
||||
fill_err(err, err_len, "codec write failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -32,10 +32,10 @@ static void wifi_event_handler(void *arg,
|
||||
}
|
||||
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
if (s_retry_num < CONFIG_LYF_WIFI_MAXIMUM_RETRY) {
|
||||
if (s_retry_num < CONFIG_TQ_WIFI_MAXIMUM_RETRY) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
ESP_LOGW(TAG, "retry to connect to the AP (%d/%d)", s_retry_num, CONFIG_LYF_WIFI_MAXIMUM_RETRY);
|
||||
ESP_LOGW(TAG, "retry to connect to the AP (%d/%d)", s_retry_num, CONFIG_TQ_WIFI_MAXIMUM_RETRY);
|
||||
} else {
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||
}
|
||||
@@ -53,13 +53,13 @@ static void wifi_event_handler(void *arg,
|
||||
|
||||
static esp_err_t start_sta_mode(void) {
|
||||
wifi_config_t wifi_config = {0};
|
||||
if (strlen(CONFIG_LYF_WIFI_SSID) == 0) {
|
||||
ESP_LOGE(TAG, "CONFIG_LYF_WIFI_SSID is empty; STA-only mode requires valid SSID");
|
||||
if (strlen(CONFIG_TQ_WIFI_SSID) == 0) {
|
||||
ESP_LOGE(TAG, "CONFIG_TQ_WIFI_SSID is empty; STA-only mode requires valid SSID");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
strlcpy((char *)wifi_config.sta.ssid, CONFIG_LYF_WIFI_SSID, sizeof(wifi_config.sta.ssid));
|
||||
strlcpy((char *)wifi_config.sta.password, CONFIG_LYF_WIFI_PASSWORD, sizeof(wifi_config.sta.password));
|
||||
strlcpy((char *)wifi_config.sta.ssid, CONFIG_TQ_WIFI_SSID, sizeof(wifi_config.sta.ssid));
|
||||
strlcpy((char *)wifi_config.sta.password, CONFIG_TQ_WIFI_PASSWORD, sizeof(wifi_config.sta.password));
|
||||
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
wifi_config.sta.pmf_cfg.capable = true;
|
||||
wifi_config.sta.pmf_cfg.required = false;
|
||||
@@ -77,13 +77,13 @@ static esp_err_t start_sta_mode(void) {
|
||||
pdMS_TO_TICKS(15000));
|
||||
|
||||
if (bits & WIFI_CONNECTED_BIT) {
|
||||
ESP_LOGI(TAG, "connected to AP SSID:%s", CONFIG_LYF_WIFI_SSID);
|
||||
ESP_LOGI(TAG, "connected to AP SSID:%s", CONFIG_TQ_WIFI_SSID);
|
||||
s_ready = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (bits & WIFI_FAIL_BIT) {
|
||||
ESP_LOGW(TAG, "Failed to connect to SSID:%s", CONFIG_LYF_WIFI_SSID);
|
||||
ESP_LOGW(TAG, "Failed to connect to SSID:%s", CONFIG_TQ_WIFI_SSID);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user