mirror of
https://github.com/adrigongv23/G26---Telemetry-Software.git
synced 2026-08-25 11:33:17 +02:00
Radio subida
This commit is contained in:
parent
5f81e54793
commit
f0db3a8aeb
147 changed files with 11343 additions and 0 deletions
109
Radio/Radio_piloto/Radio_piloto.ino
Normal file
109
Radio/Radio_piloto/Radio_piloto.ino
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include <Arduino.h>
|
||||
|
||||
// Evita que Arduino-ESP32 libere la memoria de Bluetooth Classic antes de setup().
|
||||
#if __has_include("esp32-hal-alloc-bt-classic-mem.h")
|
||||
#include "esp32-hal-alloc-bt-classic-mem.h"
|
||||
#elif __has_include("esp32-hal-bt-mem.h")
|
||||
#include "esp32-hal-bt-mem.h"
|
||||
#else
|
||||
#error "Actualiza esp32 by Espressif Systems a la version 3.3.8 o posterior."
|
||||
#endif
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "config.hpp"
|
||||
#include "ptt.hpp"
|
||||
#include "uart_audio.hpp"
|
||||
|
||||
static uint8_t audio_frame_sequence = 0;
|
||||
static TaskHandle_t pilot_tx_task_handle = nullptr;
|
||||
|
||||
static void pilot_tx_task(void *parameter)
|
||||
{
|
||||
(void)parameter;
|
||||
bool microphone_enabled = false;
|
||||
|
||||
for (;;) {
|
||||
const bool ptt_pressed = ptt_is_pressed();
|
||||
|
||||
if (ptt_pressed != microphone_enabled) {
|
||||
microphone_enabled = ptt_pressed;
|
||||
audio_set_microphone_enabled(microphone_enabled);
|
||||
}
|
||||
|
||||
if (!ptt_pressed) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
const int8_t *audio = nullptr;
|
||||
uint16_t audio_len = 0;
|
||||
|
||||
if (audio_capture_frame(&audio, &audio_len) && ptt_is_pressed()) {
|
||||
uart_send_audio(audio_frame_sequence++, audio, audio_len);
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
static void stop_forever(const char *reason)
|
||||
{
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println(reason);
|
||||
#endif
|
||||
while (true) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(500);
|
||||
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println();
|
||||
Serial.println("================================================");
|
||||
Serial.println(" Radio piloto: INTERCOM BLUETOOTH <-> UART");
|
||||
Serial.println("================================================");
|
||||
#endif
|
||||
|
||||
if (!ptt_init()) {
|
||||
stop_forever("FALLO: no se pudo iniciar el PTT KEY2");
|
||||
}
|
||||
|
||||
if (!audio_init()) {
|
||||
stop_forever("FALLO: no se pudo iniciar Bluetooth HFP");
|
||||
}
|
||||
|
||||
if (!uart_audio_init()) {
|
||||
stop_forever("FALLO: no se pudo iniciar el enlace UART");
|
||||
}
|
||||
|
||||
const BaseType_t tx_task_result = xTaskCreatePinnedToCore(
|
||||
pilot_tx_task,
|
||||
"pilot_audio_tx",
|
||||
PILOT_TX_TASK_STACK,
|
||||
nullptr,
|
||||
PILOT_TX_TASK_PRIORITY,
|
||||
&pilot_tx_task_handle,
|
||||
PILOT_TX_TASK_CORE
|
||||
);
|
||||
|
||||
if (tx_task_result != pdPASS) {
|
||||
stop_forever("FALLO: no se pudo iniciar la tarea de transmision del piloto");
|
||||
}
|
||||
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("Radio lista. Enciende el V6 Pro+; se conectara automaticamente.");
|
||||
Serial.println("KEY2 pulsado = enviar micro; la escucha del box sigue activa.");
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
audio_process();
|
||||
uart_process_received_audio(true);
|
||||
uart_audio_debug_report();
|
||||
delay(1);
|
||||
}
|
||||
767
Radio/Radio_piloto/audio.cpp
Normal file
767
Radio/Radio_piloto/audio.cpp
Normal file
|
|
@ -0,0 +1,767 @@
|
|||
#include "audio.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp32-hal-bt.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_hf_ag_api.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/stream_buffer.h"
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32)
|
||||
#error "Bluetooth HFP necesita un ESP32 clasico, como el ESP32-A1S."
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI) || !(CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI)
|
||||
#error "Instala esp32 by Espressif Systems 3.3.8 o posterior para usar HFP por HCI."
|
||||
#endif
|
||||
|
||||
/* ============================================================
|
||||
ESTADO BLUETOOTH
|
||||
============================================================ */
|
||||
|
||||
static esp_bd_addr_t intercom_addr = INTERCOM_BT_MAC;
|
||||
static char test_number[] = "100";
|
||||
|
||||
static volatile bool hfp_ready = false;
|
||||
static volatile bool slc_connected = false;
|
||||
static volatile bool slc_connecting = false;
|
||||
static volatile bool audio_connected = false;
|
||||
static volatile bool audio_connecting = false;
|
||||
static volatile bool call_active = false;
|
||||
static volatile bool codec_msbc = false;
|
||||
|
||||
static uint32_t last_slc_attempt_ms = 0;
|
||||
static uint32_t last_audio_attempt_ms = 0;
|
||||
static uint32_t call_start_at_ms = 0;
|
||||
static uint32_t audio_open_at_ms = 0;
|
||||
|
||||
/* ============================================================
|
||||
BUFFERS DE AUDIO
|
||||
|
||||
mic_buffer:
|
||||
PCM del micro ya convertido a 8 bits / 8 kHz para la radio.
|
||||
|
||||
speaker_buffer:
|
||||
PCM de 16 bits a la frecuencia negociada por HFP para el intercom.
|
||||
============================================================ */
|
||||
|
||||
static StreamBufferHandle_t mic_buffer = nullptr;
|
||||
static StreamBufferHandle_t speaker_buffer = nullptr;
|
||||
static esp_timer_handle_t hfp_audio_timer = nullptr;
|
||||
|
||||
static volatile bool microphone_enabled = false;
|
||||
static volatile bool playback_enabled = false;
|
||||
static volatile bool speaker_clear_requested = true;
|
||||
static volatile bool downsample_phase = false;
|
||||
|
||||
static int8_t mic_frame[AUDIO_SAMPLES_PER_BLOCK];
|
||||
|
||||
/* ============================================================
|
||||
UTILIDADES
|
||||
============================================================ */
|
||||
|
||||
static void print_esp_result(const char *name, esp_err_t err)
|
||||
{
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
if (err == ESP_OK) {
|
||||
Serial.printf("%s: OK\n", name);
|
||||
} else {
|
||||
Serial.printf("%s: %s (0x%04X)\n", name, esp_err_to_name(err), (unsigned)err);
|
||||
}
|
||||
#else
|
||||
(void)name;
|
||||
(void)err;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void print_intercom_mac()
|
||||
{
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.printf(
|
||||
"%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
intercom_addr[0], intercom_addr[1], intercom_addr[2],
|
||||
intercom_addr[3], intercom_addr[4], intercom_addr[5]
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void drain_microphone_buffer()
|
||||
{
|
||||
if (mic_buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t discarded[128];
|
||||
while (xStreamBufferReceive(mic_buffer, discarded, sizeof(discarded), 0) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
static void drain_speaker_buffer_from_reader(uint8_t *temporary, size_t temporary_len)
|
||||
{
|
||||
if (speaker_buffer == nullptr || temporary == nullptr || temporary_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (xStreamBufferReceive(speaker_buffer, temporary, temporary_len, 0) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
PIPELINE HFP
|
||||
============================================================ */
|
||||
|
||||
static void audio_timer_callback(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
if (audio_connected) {
|
||||
esp_hf_ag_outgoing_data_ready();
|
||||
}
|
||||
}
|
||||
|
||||
static void stop_hfp_audio_timer()
|
||||
{
|
||||
if (hfp_audio_timer != nullptr) {
|
||||
esp_timer_stop(hfp_audio_timer);
|
||||
esp_timer_delete(hfp_audio_timer);
|
||||
hfp_audio_timer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static bool start_hfp_audio_timer()
|
||||
{
|
||||
stop_hfp_audio_timer();
|
||||
|
||||
esp_timer_create_args_t timer_args = {};
|
||||
timer_args.callback = audio_timer_callback;
|
||||
timer_args.arg = nullptr;
|
||||
timer_args.dispatch_method = ESP_TIMER_TASK;
|
||||
timer_args.name = "hfp_audio";
|
||||
timer_args.skip_unhandled_events = true;
|
||||
|
||||
esp_err_t err = esp_timer_create(&timer_args, &hfp_audio_timer);
|
||||
if (err != ESP_OK) {
|
||||
print_esp_result("esp_timer_create", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
err = esp_timer_start_periodic(
|
||||
hfp_audio_timer,
|
||||
codec_msbc ? INTERCOM_MSBC_TRIGGER_US : INTERCOM_CVSD_TRIGGER_US
|
||||
);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
print_esp_result("esp_timer_start_periodic", err);
|
||||
stop_hfp_audio_timer();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
HFP entrega PCM mono de 16 bits.
|
||||
- CVSD: 8 kHz -> se usa cada muestra.
|
||||
- mSBC: 16 kHz -> se toma una de cada dos muestras.
|
||||
|
||||
No se aplica paso alto, paso bajo, puerta de ruido ni ganancia digital.
|
||||
*/
|
||||
static void incoming_audio_callback(const uint8_t *buf, uint32_t len)
|
||||
{
|
||||
if (buf == nullptr || len < sizeof(int16_t) || !microphone_enabled || mic_buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t *samples = reinterpret_cast<const int16_t *>(buf);
|
||||
const size_t sample_count = len / sizeof(int16_t);
|
||||
int8_t converted[128];
|
||||
size_t converted_count = 0;
|
||||
|
||||
for (size_t i = 0; i < sample_count; ++i) {
|
||||
bool keep = true;
|
||||
|
||||
if (codec_msbc) {
|
||||
keep = !downsample_phase;
|
||||
downsample_phase = !downsample_phase;
|
||||
}
|
||||
|
||||
if (!keep) {
|
||||
continue;
|
||||
}
|
||||
|
||||
converted[converted_count++] = (int8_t)(samples[i] / 256);
|
||||
|
||||
if (converted_count == sizeof(converted)) {
|
||||
xStreamBufferSend(mic_buffer, converted, converted_count, 0);
|
||||
converted_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (converted_count > 0) {
|
||||
xStreamBufferSend(mic_buffer, converted, converted_count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
El intercom solicita PCM mono de 16 bits.
|
||||
Si no hay audio del box disponible, se devuelve silencio manteniendo
|
||||
abierto el enlace HFP.
|
||||
*/
|
||||
static uint32_t outgoing_audio_callback(uint8_t *buf, uint32_t len)
|
||||
{
|
||||
if (buf == nullptr || len == 0 || !audio_connected) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (speaker_clear_requested) {
|
||||
drain_speaker_buffer_from_reader(buf, len);
|
||||
speaker_clear_requested = false;
|
||||
}
|
||||
|
||||
if (!playback_enabled || speaker_buffer == nullptr) {
|
||||
memset(buf, 0, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
const size_t received = xStreamBufferReceive(speaker_buffer, buf, len, 0);
|
||||
if (received < len) {
|
||||
memset(buf + received, 0, len - received);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
CONEXION HFP
|
||||
============================================================ */
|
||||
|
||||
static void request_slc_connect(bool force)
|
||||
{
|
||||
if (!hfp_ready || slc_connected || slc_connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
if (!force && (uint32_t)(now - last_slc_attempt_ms) < INTERCOM_SLC_RETRY_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_slc_attempt_ms = now;
|
||||
slc_connecting = true;
|
||||
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.print("Conectando intercom HFP: ");
|
||||
print_intercom_mac();
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
const esp_err_t err = esp_hf_ag_slc_connect(intercom_addr);
|
||||
if (err != ESP_OK) {
|
||||
slc_connecting = false;
|
||||
}
|
||||
print_esp_result("esp_hf_ag_slc_connect", err);
|
||||
}
|
||||
|
||||
static void start_fake_call()
|
||||
{
|
||||
if (!slc_connected || call_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
call_active = true;
|
||||
const esp_err_t err = esp_hf_ag_out_call(
|
||||
intercom_addr,
|
||||
1,
|
||||
0,
|
||||
ESP_HF_CALL_STATUS_CALL_IN_PROGRESS,
|
||||
ESP_HF_CALL_SETUP_STATUS_IDLE,
|
||||
test_number,
|
||||
ESP_HF_CALL_ADDR_TYPE_UNKNOWN
|
||||
);
|
||||
|
||||
print_esp_result("esp_hf_ag_out_call", err);
|
||||
audio_open_at_ms = millis() + INTERCOM_AUDIO_OPEN_DELAY_MS;
|
||||
}
|
||||
|
||||
static void request_audio_connect(bool force)
|
||||
{
|
||||
if (!slc_connected || audio_connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
if (!force && audio_connecting &&
|
||||
(uint32_t)(now - last_audio_attempt_ms) < INTERCOM_AUDIO_RETRY_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
last_audio_attempt_ms = now;
|
||||
audio_connecting = true;
|
||||
|
||||
const esp_err_t err = esp_hf_ag_audio_connect(intercom_addr);
|
||||
if (err != ESP_OK) {
|
||||
audio_connecting = false;
|
||||
}
|
||||
print_esp_result("esp_hf_ag_audio_connect", err);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
RESPUESTAS HFP BASICAS
|
||||
============================================================ */
|
||||
|
||||
static void send_indicator_state(esp_bd_addr_t addr)
|
||||
{
|
||||
esp_hf_ag_ciev_report(addr, ESP_HF_IND_TYPE_CALL,
|
||||
call_active ? ESP_HF_CALL_STATUS_CALL_IN_PROGRESS : ESP_HF_CALL_STATUS_NO_CALLS);
|
||||
esp_hf_ag_ciev_report(addr, ESP_HF_IND_TYPE_CALLSETUP, ESP_HF_CALL_SETUP_STATUS_IDLE);
|
||||
esp_hf_ag_ciev_report(addr, ESP_HF_IND_TYPE_SERVICE, ESP_HF_NETWORK_STATE_AVAILABLE);
|
||||
esp_hf_ag_ciev_report(addr, ESP_HF_IND_TYPE_SIGNAL, 5);
|
||||
esp_hf_ag_ciev_report(addr, ESP_HF_IND_TYPE_ROAM, ESP_HF_ROAMING_STATUS_INACTIVE);
|
||||
esp_hf_ag_ciev_report(addr, ESP_HF_IND_TYPE_BATTCHG, 5);
|
||||
esp_hf_ag_ciev_report(addr, ESP_HF_IND_TYPE_CALLHELD, ESP_HF_CALL_HELD_STATUS_NONE);
|
||||
}
|
||||
|
||||
static void answer_current_call(esp_bd_addr_t addr)
|
||||
{
|
||||
call_active = true;
|
||||
print_esp_result(
|
||||
"esp_hf_ag_answer_call",
|
||||
esp_hf_ag_answer_call(
|
||||
addr,
|
||||
1,
|
||||
0,
|
||||
ESP_HF_CALL_STATUS_CALL_IN_PROGRESS,
|
||||
ESP_HF_CALL_SETUP_STATUS_IDLE,
|
||||
test_number,
|
||||
ESP_HF_CALL_ADDR_TYPE_UNKNOWN
|
||||
)
|
||||
);
|
||||
audio_open_at_ms = millis() + INTERCOM_AUDIO_OPEN_DELAY_MS;
|
||||
}
|
||||
|
||||
static void gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_BT_GAP_AUTH_CMPL_EVT:
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println(param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS
|
||||
? "Intercom emparejado correctamente"
|
||||
: "ERROR emparejando el intercom");
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
|
||||
case ESP_BT_GAP_PIN_REQ_EVT: {
|
||||
esp_bt_pin_code_t pin = {'0', '0', '0', '0'};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void hfp_callback(esp_hf_cb_event_t event, esp_hf_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_HF_PROF_STATE_EVT:
|
||||
hfp_ready = (param->prof_stat.state == ESP_HF_INIT_SUCCESS ||
|
||||
param->prof_stat.state == ESP_HF_INIT_ALREADY);
|
||||
if (hfp_ready) {
|
||||
request_slc_connect(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_HF_CONNECTION_STATE_EVT: {
|
||||
const esp_hf_connection_state_t state = param->conn_stat.state;
|
||||
slc_connected = (state == ESP_HF_CONNECTION_STATE_SLC_CONNECTED);
|
||||
slc_connecting = (state == ESP_HF_CONNECTION_STATE_CONNECTING ||
|
||||
state == ESP_HF_CONNECTION_STATE_CONNECTED);
|
||||
|
||||
if (state == ESP_HF_CONNECTION_STATE_DISCONNECTED) {
|
||||
slc_connected = false;
|
||||
slc_connecting = false;
|
||||
audio_connected = false;
|
||||
audio_connecting = false;
|
||||
call_active = false;
|
||||
call_start_at_ms = 0;
|
||||
audio_open_at_ms = 0;
|
||||
microphone_enabled = false;
|
||||
speaker_clear_requested = true;
|
||||
stop_hfp_audio_timer();
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("Intercom desconectado. Se reintentara automaticamente.");
|
||||
#endif
|
||||
} else if (slc_connected) {
|
||||
slc_connecting = false;
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("Intercom HFP conectado");
|
||||
#endif
|
||||
esp_hf_ag_volume_control(intercom_addr, ESP_HF_VOLUME_CONTROL_TARGET_SPK, 12);
|
||||
esp_hf_ag_volume_control(intercom_addr, ESP_HF_VOLUME_CONTROL_TARGET_MIC, 15);
|
||||
call_start_at_ms = millis() + INTERCOM_CALL_DELAY_MS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_AUDIO_STATE_EVT: {
|
||||
const esp_hf_audio_state_t state = param->audio_stat.state;
|
||||
audio_connecting = (state == ESP_HF_AUDIO_STATE_CONNECTING);
|
||||
audio_connected = (state == ESP_HF_AUDIO_STATE_CONNECTED ||
|
||||
state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC);
|
||||
codec_msbc = (state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC);
|
||||
|
||||
if (audio_connected) {
|
||||
audio_connecting = false;
|
||||
downsample_phase = false;
|
||||
speaker_clear_requested = true;
|
||||
esp_hf_ag_register_data_callback(incoming_audio_callback, outgoing_audio_callback);
|
||||
start_hfp_audio_timer();
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.printf("Audio del intercom conectado: %s\n",
|
||||
codec_msbc ? "mSBC 16 kHz" : "CVSD 8 kHz");
|
||||
#endif
|
||||
} else if (state == ESP_HF_AUDIO_STATE_DISCONNECTED) {
|
||||
audio_connecting = false;
|
||||
stop_hfp_audio_timer();
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("Audio del intercom desconectado");
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_IND_UPDATE_EVT:
|
||||
send_indicator_state(param->ind_upd.remote_addr);
|
||||
break;
|
||||
|
||||
case ESP_HF_CIND_RESPONSE_EVT:
|
||||
esp_hf_ag_cind_response(
|
||||
param->cind_rep.remote_addr,
|
||||
call_active ? ESP_HF_CALL_STATUS_CALL_IN_PROGRESS : ESP_HF_CALL_STATUS_NO_CALLS,
|
||||
ESP_HF_CALL_SETUP_STATUS_IDLE,
|
||||
ESP_HF_NETWORK_STATE_AVAILABLE,
|
||||
5,
|
||||
ESP_HF_ROAMING_STATUS_INACTIVE,
|
||||
5,
|
||||
ESP_HF_CALL_HELD_STATUS_NONE
|
||||
);
|
||||
break;
|
||||
|
||||
case ESP_HF_COPS_RESPONSE_EVT: {
|
||||
static char operator_name[] = "FormulaGades";
|
||||
esp_hf_ag_cops_response(param->cops_rep.remote_addr, operator_name);
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_HF_CLCC_RESPONSE_EVT:
|
||||
if (call_active) {
|
||||
esp_hf_ag_clcc_response(
|
||||
param->clcc_rep.remote_addr,
|
||||
1,
|
||||
ESP_HF_CURRENT_CALL_DIRECTION_OUTGOING,
|
||||
ESP_HF_CURRENT_CALL_STATUS_ACTIVE,
|
||||
ESP_HF_CURRENT_CALL_MODE_VOICE,
|
||||
ESP_HF_CURRENT_CALL_MPTY_TYPE_SINGLE,
|
||||
test_number,
|
||||
ESP_HF_CALL_ADDR_TYPE_UNKNOWN
|
||||
);
|
||||
}
|
||||
esp_hf_ag_clcc_response(
|
||||
param->clcc_rep.remote_addr,
|
||||
0,
|
||||
ESP_HF_CURRENT_CALL_DIRECTION_OUTGOING,
|
||||
ESP_HF_CURRENT_CALL_STATUS_ACTIVE,
|
||||
ESP_HF_CURRENT_CALL_MODE_VOICE,
|
||||
ESP_HF_CURRENT_CALL_MPTY_TYPE_SINGLE,
|
||||
test_number,
|
||||
ESP_HF_CALL_ADDR_TYPE_UNKNOWN
|
||||
);
|
||||
break;
|
||||
|
||||
case ESP_HF_CNUM_RESPONSE_EVT:
|
||||
esp_hf_ag_cnum_response(
|
||||
param->cnum_rep.remote_addr,
|
||||
test_number,
|
||||
129,
|
||||
ESP_HF_SUBSCRIBER_SERVICE_TYPE_VOICE
|
||||
);
|
||||
break;
|
||||
|
||||
case ESP_HF_ATA_RESPONSE_EVT:
|
||||
answer_current_call(param->ata_rep.remote_addr);
|
||||
break;
|
||||
|
||||
case ESP_HF_CHUP_RESPONSE_EVT:
|
||||
call_active = false;
|
||||
audio_connected = false;
|
||||
audio_connecting = false;
|
||||
stop_hfp_audio_timer();
|
||||
break;
|
||||
|
||||
case ESP_HF_DIAL_EVT:
|
||||
esp_hf_ag_cmee_send(
|
||||
param->out_call.remote_addr,
|
||||
ESP_HF_AT_RESPONSE_CODE_OK,
|
||||
ESP_HF_CME_AG_FAILURE
|
||||
);
|
||||
answer_current_call(param->out_call.remote_addr);
|
||||
break;
|
||||
|
||||
case ESP_HF_UNAT_RESPONSE_EVT: {
|
||||
// El V6 Pro+ envia +SUGCODEC=1. Hay que responder OK para completar SLC.
|
||||
static char ok_response[] = "OK";
|
||||
esp_hf_ag_unknown_at_send(param->unat_rep.remote_addr, ok_response);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool init_bluetooth()
|
||||
{
|
||||
if (mic_buffer == nullptr) {
|
||||
mic_buffer = xStreamBufferCreate(INTERCOM_MIC_BUFFER_BYTES, 1);
|
||||
}
|
||||
if (speaker_buffer == nullptr) {
|
||||
speaker_buffer = xStreamBufferCreate(INTERCOM_SPEAKER_BUFFER_BYTES, 1);
|
||||
}
|
||||
|
||||
if (mic_buffer == nullptr || speaker_buffer == nullptr) {
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("ERROR creando buffers de audio Bluetooth");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!btStarted() && !btStartMode(BT_MODE_CLASSIC_BT)) {
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("ERROR iniciando Bluetooth Classic");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (esp_bredr_tx_power_set(ESP_PWR_LVL_P9, ESP_PWR_LVL_P9) != ESP_OK) {
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("ERROR configurando Bluetooth Classic a maxima potencia");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_bluedroid_status_t status = esp_bluedroid_get_status();
|
||||
if (status == ESP_BLUEDROID_STATUS_UNINITIALIZED) {
|
||||
if (esp_bluedroid_init() != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
status = esp_bluedroid_get_status();
|
||||
if (status != ESP_BLUEDROID_STATUS_ENABLED) {
|
||||
if (esp_bluedroid_enable() != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (esp_bt_gap_register_callback(gap_callback) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_bt_io_cap_t io_capability = ESP_BT_IO_CAP_NONE;
|
||||
esp_bt_gap_set_security_param(ESP_BT_SP_IOCAP_MODE, &io_capability, sizeof(io_capability));
|
||||
|
||||
esp_bt_pin_code_t unused_pin = {0};
|
||||
esp_bt_gap_set_pin(ESP_BT_PIN_TYPE_VARIABLE, 0, unused_pin);
|
||||
esp_bt_dev_set_device_name(INTERCOM_BT_NAME);
|
||||
|
||||
esp_bt_cod_t cod = {};
|
||||
cod.major = ESP_BT_COD_MAJOR_DEV_PHONE;
|
||||
cod.minor = 0x00;
|
||||
cod.service = ESP_BT_COD_SRVC_AUDIO | ESP_BT_COD_SRVC_TELEPHONY;
|
||||
esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_ALL);
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
|
||||
if (esp_bredr_sco_datapath_set(ESP_SCO_DATA_PATH_HCI) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
if (esp_hf_ag_register_callback(hfp_callback) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
if (esp_hf_ag_register_data_callback(incoming_audio_callback, outgoing_audio_callback) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
if (esp_hf_ag_init() != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
API USADA POR RADIO_PILOTO
|
||||
============================================================ */
|
||||
|
||||
bool audio_init()
|
||||
{
|
||||
playback_enabled = false;
|
||||
speaker_clear_requested = true;
|
||||
microphone_enabled = false;
|
||||
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.print("Intercom configurado: ");
|
||||
print_intercom_mac();
|
||||
Serial.println();
|
||||
#endif
|
||||
|
||||
return init_bluetooth();
|
||||
}
|
||||
|
||||
void audio_process()
|
||||
{
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (hfp_ready && !slc_connected) {
|
||||
request_slc_connect(false);
|
||||
}
|
||||
|
||||
if (slc_connected && call_start_at_ms != 0 &&
|
||||
(int32_t)(now - call_start_at_ms) >= 0) {
|
||||
call_start_at_ms = 0;
|
||||
start_fake_call();
|
||||
}
|
||||
|
||||
if (slc_connected && call_active && audio_open_at_ms != 0 &&
|
||||
(int32_t)(now - audio_open_at_ms) >= 0) {
|
||||
audio_open_at_ms = 0;
|
||||
request_audio_connect(true);
|
||||
}
|
||||
|
||||
if (slc_connected && call_active && !audio_connected &&
|
||||
(uint32_t)(now - last_audio_attempt_ms) >= INTERCOM_AUDIO_RETRY_MS) {
|
||||
request_audio_connect(false);
|
||||
}
|
||||
}
|
||||
|
||||
void audio_set_microphone_enabled(bool enabled)
|
||||
{
|
||||
microphone_enabled = false;
|
||||
drain_microphone_buffer();
|
||||
downsample_phase = false;
|
||||
microphone_enabled = enabled;
|
||||
}
|
||||
|
||||
bool audio_capture_frame(const int8_t **out_audio, uint16_t *out_len)
|
||||
{
|
||||
if (out_audio == nullptr || out_len == nullptr || mic_buffer == nullptr ||
|
||||
!microphone_enabled || !audio_connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
const uint32_t started = millis();
|
||||
|
||||
while (total < AUDIO_SAMPLES_PER_BLOCK) {
|
||||
if (!microphone_enabled || !audio_connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t received = xStreamBufferReceive(
|
||||
mic_buffer,
|
||||
mic_frame + total,
|
||||
AUDIO_SAMPLES_PER_BLOCK - total,
|
||||
pdMS_TO_TICKS(20)
|
||||
);
|
||||
|
||||
total += received;
|
||||
|
||||
if ((uint32_t)(millis() - started) >= INTERCOM_MIC_FRAME_TIMEOUT_MS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*out_audio = mic_frame;
|
||||
*out_len = AUDIO_SAMPLES_PER_BLOCK;
|
||||
return true;
|
||||
}
|
||||
|
||||
void audio_play_frame_s8(const int8_t *audio, uint16_t len)
|
||||
{
|
||||
if (audio == nullptr || len == 0 || speaker_buffer == nullptr ||
|
||||
!playback_enabled || !audio_connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > AUDIO_MAX_PAYLOAD) {
|
||||
len = AUDIO_MAX_PAYLOAD;
|
||||
}
|
||||
|
||||
const size_t required_bytes = (size_t)len * sizeof(int16_t) * (codec_msbc ? 2U : 1U);
|
||||
if (xStreamBufferSpacesAvailable(speaker_buffer) < required_bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr uint16_t INPUT_CHUNK = 160;
|
||||
int16_t converted[INPUT_CHUNK * 2];
|
||||
|
||||
uint16_t position = 0;
|
||||
while (position < len) {
|
||||
uint16_t chunk = (uint16_t)(len - position);
|
||||
if (chunk > INPUT_CHUNK) {
|
||||
chunk = INPUT_CHUNK;
|
||||
}
|
||||
|
||||
size_t output_samples = 0;
|
||||
for (uint16_t i = 0; i < chunk; ++i) {
|
||||
const int16_t sample = (int16_t)((int32_t)audio[position + i] * 256);
|
||||
converted[output_samples++] = sample;
|
||||
|
||||
if (codec_msbc) {
|
||||
converted[output_samples++] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t bytes = output_samples * sizeof(int16_t);
|
||||
const size_t written = xStreamBufferSend(
|
||||
speaker_buffer,
|
||||
reinterpret_cast<const uint8_t *>(converted),
|
||||
bytes,
|
||||
0
|
||||
);
|
||||
|
||||
if (written != bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
position = (uint16_t)(position + chunk);
|
||||
}
|
||||
}
|
||||
|
||||
void audio_request_playback_stop()
|
||||
{
|
||||
playback_enabled = false;
|
||||
speaker_clear_requested = true;
|
||||
}
|
||||
|
||||
void audio_resume_playback()
|
||||
{
|
||||
playback_enabled = true;
|
||||
}
|
||||
|
||||
void audio_cut_playback()
|
||||
{
|
||||
playback_enabled = false;
|
||||
speaker_clear_requested = true;
|
||||
}
|
||||
14
Radio/Radio_piloto/audio.hpp
Normal file
14
Radio/Radio_piloto/audio.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
bool audio_init();
|
||||
void audio_process();
|
||||
|
||||
void audio_set_microphone_enabled(bool enabled);
|
||||
bool audio_capture_frame(const int8_t **out_audio, uint16_t *out_len);
|
||||
|
||||
void audio_play_frame_s8(const int8_t *audio, uint16_t len);
|
||||
void audio_request_playback_stop();
|
||||
void audio_resume_playback();
|
||||
void audio_cut_playback();
|
||||
87
Radio/Radio_piloto/config.hpp
Normal file
87
Radio/Radio_piloto/config.hpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/* ============================================================
|
||||
AUDIO DE LA RADIO
|
||||
|
||||
El protocolo de radio sigue usando audio mono de 8 bits a 8 kHz.
|
||||
El intercom negocia normalmente mSBC a 16 kHz; audio.cpp hace la
|
||||
conversión sencilla 16 kHz <-> 8 kHz sin aplicar filtros.
|
||||
============================================================ */
|
||||
|
||||
#define AUDIO_SAMPLE_RATE 8000
|
||||
#define AUDIO_BLOCK_MS 200
|
||||
#define AUDIO_SAMPLES_PER_BLOCK ((AUDIO_SAMPLE_RATE * AUDIO_BLOCK_MS) / 1000)
|
||||
#define AUDIO_MAX_PAYLOAD AUDIO_SAMPLES_PER_BLOCK
|
||||
|
||||
/* ============================================================
|
||||
INTERCOM BLUETOOTH HFP
|
||||
|
||||
ESP32-A1S = Audio Gateway (como un teléfono)
|
||||
V6 Pro+ = manos libres HFP
|
||||
============================================================ */
|
||||
|
||||
#define INTERCOM_BT_NAME "FORMULA_GADES_HFP"
|
||||
#define INTERCOM_BT_MAC {0x10, 0xDC, 0xB6, 0x74, 0x2D, 0x23}
|
||||
#define INTERCOM_SLC_RETRY_MS 5000
|
||||
#define INTERCOM_AUDIO_RETRY_MS 3000
|
||||
#define INTERCOM_CALL_DELAY_MS 1200
|
||||
#define INTERCOM_AUDIO_OPEN_DELAY_MS 600
|
||||
#define INTERCOM_MIC_BUFFER_BYTES 6400
|
||||
#define INTERCOM_SPEAKER_BUFFER_BYTES 32768
|
||||
#define INTERCOM_CVSD_TRIGGER_US 4000
|
||||
#define INTERCOM_MSBC_TRIGGER_US 7500
|
||||
#define INTERCOM_MIC_FRAME_TIMEOUT_MS 500
|
||||
|
||||
/* ============================================================
|
||||
PTT CON EL BOTON KEY2 DE LA ESP32-AUDIO-KIT
|
||||
|
||||
Switch 1 (IO13-KEY2): ON
|
||||
Switch 2 (IO13-DATA3): OFF
|
||||
Switch 4 (IO13-MTCK): OFF
|
||||
============================================================ */
|
||||
|
||||
#define PTT_BUTTON_GPIO GPIO_NUM_13
|
||||
#define PTT_BUTTON_ACTIVE_LEVEL LOW
|
||||
#define PTT_DEBOUNCE_MS 25
|
||||
#define PTT_BUTTON_DEBUG 0
|
||||
|
||||
/* ============================================================
|
||||
UART CON EL ESP32-WROOM-32U INTERMEDIARIO
|
||||
|
||||
Audio Kit GPIO23 (TX) -> WROOM-U GPIO16 (RX)
|
||||
Audio Kit GPIO22 (RX) <- WROOM-U GPIO17 (TX)
|
||||
GND Audio Kit <-> GND WROOM-U
|
||||
============================================================ */
|
||||
|
||||
#define PILOT_UART_PORT 2
|
||||
#define PILOT_UART_BAUD 460800
|
||||
#define PILOT_UART_RX_GPIO 22
|
||||
#define PILOT_UART_TX_GPIO 23
|
||||
#define PILOT_UART_RX_BUFFER_SIZE 8192
|
||||
#define PILOT_UART_PACKET_QUEUE_SIZE 32
|
||||
#define PILOT_UART_TASK_STACK 4096
|
||||
#define PILOT_UART_TASK_PRIORITY 4
|
||||
#define PILOT_UART_TASK_CORE 1
|
||||
#define PILOT_UART_PARSER_TIMEOUT_MS 120
|
||||
#define PILOT_UART_WRITE_TIMEOUT_MS 120
|
||||
|
||||
#define PILOT_TX_TASK_STACK 6144
|
||||
#define PILOT_TX_TASK_PRIORITY 3
|
||||
#define PILOT_TX_TASK_CORE 1
|
||||
|
||||
#define RADIO_AUDIO_CHUNK 200
|
||||
#define RADIO_PACKET_HEADER_LEN 11
|
||||
#define RADIO_PACKET_MAX_LEN (RADIO_PACKET_HEADER_LEN + RADIO_AUDIO_CHUNK)
|
||||
#define RADIO_REASSEMBLY_TIMEOUT_MS 600
|
||||
#define BOX_AUDIO_AUTOSTART_GUARD_MS 150
|
||||
|
||||
#define RADIO_AUDIO_MAGIC_1 0xE5
|
||||
#define RADIO_AUDIO_MAGIC_2 0x5E
|
||||
#define RADIO_CONTROL_MAGIC_1 0xE5
|
||||
#define RADIO_CONTROL_MAGIC_2 0x5F
|
||||
#define RADIO_CONTROL_COMMAND_BOX_PTT 0x01
|
||||
#define RADIO_CONTROL_PACKET_LEN 4
|
||||
|
||||
#define PILOT_DEBUG_SERIAL 1
|
||||
62
Radio/Radio_piloto/ptt.cpp
Normal file
62
Radio/Radio_piloto/ptt.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include "ptt.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
static bool stable_pressed = false;
|
||||
static bool last_raw_pressed = false;
|
||||
static uint32_t last_raw_change_ms = 0;
|
||||
|
||||
bool ptt_init()
|
||||
{
|
||||
// KEY2 no lleva resistencia pull-up externa, por lo que activamos
|
||||
// la pull-up interna. Al pulsar KEY2, GPIO13 queda conectado a GND.
|
||||
pinMode(PTT_BUTTON_GPIO, INPUT_PULLUP);
|
||||
delay(10);
|
||||
|
||||
last_raw_pressed =
|
||||
(digitalRead(PTT_BUTTON_GPIO) == PTT_BUTTON_ACTIVE_LEVEL);
|
||||
stable_pressed = last_raw_pressed;
|
||||
last_raw_change_ms = millis();
|
||||
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("PTT configurado en KEY2 (GPIO13, activo a nivel LOW)");
|
||||
Serial.println("DIP: 1=ON, 2=OFF y 4=OFF para usar KEY2 sin conflictos");
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ptt_is_pressed()
|
||||
{
|
||||
const bool raw_pressed =
|
||||
(digitalRead(PTT_BUTTON_GPIO) == PTT_BUTTON_ACTIVE_LEVEL);
|
||||
|
||||
// Reinicia el temporizador cada vez que cambia la lectura instantanea.
|
||||
if (raw_pressed != last_raw_pressed) {
|
||||
last_raw_pressed = raw_pressed;
|
||||
last_raw_change_ms = millis();
|
||||
}
|
||||
|
||||
// Solo acepta el nuevo estado cuando se mantiene estable el tiempo
|
||||
// indicado. Esto elimina los rebotes mecanicos del pulsador.
|
||||
if (stable_pressed != last_raw_pressed &&
|
||||
(uint32_t)(millis() - last_raw_change_ms) >= PTT_DEBOUNCE_MS) {
|
||||
stable_pressed = last_raw_pressed;
|
||||
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println(stable_pressed
|
||||
? "PTT KEY2 ACTIVADO"
|
||||
: "PTT KEY2 DESACTIVADO");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if PTT_BUTTON_DEBUG
|
||||
Serial.print("KEY2 raw=");
|
||||
Serial.print(raw_pressed ? "PULSADO" : "LIBRE");
|
||||
Serial.print(" estable=");
|
||||
Serial.println(stable_pressed ? "PULSADO" : "LIBRE");
|
||||
#endif
|
||||
|
||||
return stable_pressed;
|
||||
}
|
||||
4
Radio/Radio_piloto/ptt.hpp
Normal file
4
Radio/Radio_piloto/ptt.hpp
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
bool ptt_init();
|
||||
bool ptt_is_pressed();
|
||||
679
Radio/Radio_piloto/uart_audio.cpp
Normal file
679
Radio/Radio_piloto/uart_audio.cpp
Normal file
|
|
@ -0,0 +1,679 @@
|
|||
#include "uart_audio.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
struct __attribute__((packed)) AudioWirePacket {
|
||||
uint8_t magic1;
|
||||
uint8_t magic2;
|
||||
uint8_t frame_seq;
|
||||
uint8_t chunk_index;
|
||||
uint8_t chunk_total;
|
||||
uint16_t frame_len;
|
||||
uint16_t offset;
|
||||
uint16_t chunk_len;
|
||||
int8_t audio[RADIO_AUDIO_CHUNK];
|
||||
};
|
||||
|
||||
struct UartRxItem {
|
||||
uint32_t epoch;
|
||||
uint16_t packet_len;
|
||||
uint8_t packet[RADIO_PACKET_MAX_LEN];
|
||||
};
|
||||
|
||||
static constexpr size_t AUDIO_HEADER_SIZE = offsetof(AudioWirePacket, audio);
|
||||
static constexpr uint8_t MAX_AUDIO_CHUNKS =
|
||||
(AUDIO_MAX_PAYLOAD + RADIO_AUDIO_CHUNK - 1) / RADIO_AUDIO_CHUNK;
|
||||
|
||||
static HardwareSerial PilotUart(PILOT_UART_PORT);
|
||||
static QueueHandle_t rx_packet_queue = nullptr;
|
||||
static TaskHandle_t rx_task_handle = nullptr;
|
||||
|
||||
static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
static volatile bool box_ptt_active = false;
|
||||
static volatile bool box_stop_pending = false;
|
||||
static volatile uint32_t box_stream_epoch = 0;
|
||||
static volatile uint32_t box_control_version = 0;
|
||||
static volatile uint32_t dropped_uart_packets = 0;
|
||||
static volatile uint32_t box_last_stop_ms = 0;
|
||||
|
||||
static volatile uint32_t debug_control_start = 0;
|
||||
static volatile uint32_t debug_control_stop = 0;
|
||||
static volatile uint32_t debug_audio_packets = 0;
|
||||
static volatile uint32_t debug_reassembled_frames = 0;
|
||||
static volatile uint32_t debug_audio_autostarts = 0;
|
||||
static volatile uint32_t debug_guard_drops = 0;
|
||||
|
||||
static int8_t reassembly_buffer[AUDIO_MAX_PAYLOAD];
|
||||
static bool chunk_received[MAX_AUDIO_CHUNKS];
|
||||
static bool reassembly_active = false;
|
||||
static uint8_t reassembly_seq = 0;
|
||||
static uint8_t reassembly_total_chunks = 0;
|
||||
static uint8_t reassembly_received_chunks = 0;
|
||||
static uint16_t reassembly_frame_len = 0;
|
||||
static uint32_t reassembly_epoch = 0;
|
||||
static uint32_t reassembly_started_ms = 0;
|
||||
|
||||
static uint16_t read_le_u16(const uint8_t *p)
|
||||
{
|
||||
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
|
||||
}
|
||||
|
||||
static bool audio_packet_valid(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (data == nullptr || len < RADIO_PACKET_HEADER_LEN || len > RADIO_PACKET_MAX_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data[0] != RADIO_AUDIO_MAGIC_1 || data[1] != RADIO_AUDIO_MAGIC_2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t chunk_index = data[3];
|
||||
const uint8_t chunk_total = data[4];
|
||||
const uint16_t frame_len = read_le_u16(&data[5]);
|
||||
const uint16_t offset = read_le_u16(&data[7]);
|
||||
const uint16_t chunk_len = read_le_u16(&data[9]);
|
||||
|
||||
if (chunk_total == 0 || chunk_total > MAX_AUDIO_CHUNKS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chunk_index >= chunk_total) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frame_len == 0 || frame_len > AUDIO_MAX_PAYLOAD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chunk_len == 0 || chunk_len > RADIO_AUDIO_CHUNK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((uint32_t)offset + chunk_len > frame_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return len == RADIO_PACKET_HEADER_LEN + chunk_len;
|
||||
}
|
||||
|
||||
static bool control_packet_valid(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
return data != nullptr &&
|
||||
len == RADIO_CONTROL_PACKET_LEN &&
|
||||
data[0] == RADIO_CONTROL_MAGIC_1 &&
|
||||
data[1] == RADIO_CONTROL_MAGIC_2 &&
|
||||
data[2] == RADIO_CONTROL_COMMAND_BOX_PTT &&
|
||||
(data[3] == 0 || data[3] == 1);
|
||||
}
|
||||
|
||||
static void snapshot_control_state(
|
||||
bool *active,
|
||||
bool *stop_pending,
|
||||
uint32_t *epoch,
|
||||
uint32_t *version)
|
||||
{
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
if (active != nullptr) {
|
||||
*active = box_ptt_active;
|
||||
}
|
||||
if (stop_pending != nullptr) {
|
||||
*stop_pending = box_stop_pending;
|
||||
}
|
||||
if (epoch != nullptr) {
|
||||
*epoch = box_stream_epoch;
|
||||
}
|
||||
if (version != nullptr) {
|
||||
*version = box_control_version;
|
||||
}
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
}
|
||||
|
||||
static void handle_control_packet(const uint8_t *packet)
|
||||
{
|
||||
const bool start_requested = packet[3] != 0;
|
||||
const uint32_t now = millis();
|
||||
bool started = false;
|
||||
bool stop_registered = false;
|
||||
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
|
||||
if (start_requested) {
|
||||
// Los START periódicos son latidos. No cambian el epoch si el stream ya
|
||||
// está activo. También cancelan un cierre pendiente si vuelve a pulsarse.
|
||||
if (!box_ptt_active) {
|
||||
box_ptt_active = true;
|
||||
++box_stream_epoch;
|
||||
++box_control_version;
|
||||
started = true;
|
||||
}
|
||||
box_stop_pending = false;
|
||||
} else {
|
||||
box_last_stop_ms = now;
|
||||
if (box_ptt_active && !box_stop_pending) {
|
||||
// No cortar todavía: todos los paquetes anteriores al STOP ya están
|
||||
// en la cola UART y deben reconstruirse/reproducirse primero.
|
||||
box_stop_pending = true;
|
||||
stop_registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
|
||||
if (started) {
|
||||
++debug_control_start;
|
||||
audio_resume_playback();
|
||||
}
|
||||
|
||||
if (stop_registered) {
|
||||
++debug_control_stop;
|
||||
}
|
||||
}
|
||||
|
||||
static void enqueue_audio_packet(const uint8_t *packet, uint16_t len)
|
||||
{
|
||||
if (rx_packet_queue == nullptr || !audio_packet_valid(packet, len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool active = false;
|
||||
bool stop_pending = false;
|
||||
uint32_t epoch = 0;
|
||||
snapshot_control_state(&active, &stop_pending, &epoch, nullptr);
|
||||
|
||||
if (stop_pending) {
|
||||
++debug_guard_drops;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!active) {
|
||||
const uint32_t now = millis();
|
||||
uint32_t last_stop = 0;
|
||||
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
last_stop = box_last_stop_ms;
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
|
||||
// Si acaba de llegar STOP, estos pueden ser fragmentos atrasados y se
|
||||
// descartan. Pasado el margen, un paquete de audio válido puede recuperar
|
||||
// por sí solo un START perdido o un reinicio del Audio Kit.
|
||||
if ((uint32_t)(now - last_stop) < BOX_AUDIO_AUTOSTART_GUARD_MS) {
|
||||
++debug_guard_drops;
|
||||
return;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
if (!box_ptt_active) {
|
||||
box_ptt_active = true;
|
||||
box_stop_pending = false;
|
||||
++box_stream_epoch;
|
||||
++box_control_version;
|
||||
++debug_audio_autostarts;
|
||||
}
|
||||
active = box_ptt_active;
|
||||
epoch = box_stream_epoch;
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
|
||||
if (active) {
|
||||
audio_resume_playback();
|
||||
}
|
||||
}
|
||||
|
||||
++debug_audio_packets;
|
||||
|
||||
UartRxItem item = {};
|
||||
item.epoch = epoch;
|
||||
item.packet_len = len;
|
||||
memcpy(item.packet, packet, len);
|
||||
|
||||
if (xQueueSend(rx_packet_queue, &item, 0) != pdTRUE) {
|
||||
// Mantener audio reciente es preferible a acumular retraso. Si la cola
|
||||
// se llena, eliminamos el paquete más antiguo e insertamos el nuevo.
|
||||
UartRxItem discarded = {};
|
||||
xQueueReceive(rx_packet_queue, &discarded, 0);
|
||||
|
||||
if (xQueueSend(rx_packet_queue, &item, 0) != pdTRUE) {
|
||||
++dropped_uart_packets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void uart_rx_task(void *parameter)
|
||||
{
|
||||
(void)parameter;
|
||||
|
||||
enum ParserState {
|
||||
WAIT_MAGIC_1,
|
||||
WAIT_MAGIC_2,
|
||||
READ_AUDIO_HEADER,
|
||||
READ_AUDIO_BODY,
|
||||
READ_CONTROL
|
||||
};
|
||||
|
||||
ParserState state = WAIT_MAGIC_1;
|
||||
uint8_t packet[RADIO_PACKET_MAX_LEN] = {};
|
||||
size_t index = 0;
|
||||
uint16_t expected_audio_len = 0;
|
||||
uint32_t last_byte_ms = millis();
|
||||
|
||||
auto reset_parser = [&]() {
|
||||
state = WAIT_MAGIC_1;
|
||||
index = 0;
|
||||
expected_audio_len = 0;
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
bool read_anything = false;
|
||||
|
||||
while (PilotUart.available() > 0) {
|
||||
const int value = PilotUart.read();
|
||||
if (value < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
read_anything = true;
|
||||
const uint8_t b = (uint8_t)value;
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (state != WAIT_MAGIC_1 &&
|
||||
(uint32_t)(now - last_byte_ms) > PILOT_UART_PARSER_TIMEOUT_MS) {
|
||||
reset_parser();
|
||||
}
|
||||
last_byte_ms = now;
|
||||
|
||||
switch (state) {
|
||||
case WAIT_MAGIC_1:
|
||||
if (b == RADIO_AUDIO_MAGIC_1) {
|
||||
packet[0] = b;
|
||||
index = 1;
|
||||
state = WAIT_MAGIC_2;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_MAGIC_2:
|
||||
if (b == RADIO_AUDIO_MAGIC_2) {
|
||||
packet[1] = b;
|
||||
index = 2;
|
||||
state = READ_AUDIO_HEADER;
|
||||
} else if (b == RADIO_CONTROL_MAGIC_2) {
|
||||
packet[1] = b;
|
||||
index = 2;
|
||||
state = READ_CONTROL;
|
||||
} else if (b == RADIO_AUDIO_MAGIC_1) {
|
||||
packet[0] = b;
|
||||
index = 1;
|
||||
} else {
|
||||
reset_parser();
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_CONTROL:
|
||||
if (index >= sizeof(packet)) {
|
||||
reset_parser();
|
||||
break;
|
||||
}
|
||||
|
||||
packet[index++] = b;
|
||||
if (index >= RADIO_CONTROL_PACKET_LEN) {
|
||||
if (control_packet_valid(packet, (uint16_t)index)) {
|
||||
handle_control_packet(packet);
|
||||
}
|
||||
reset_parser();
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_AUDIO_HEADER:
|
||||
if (index >= sizeof(packet)) {
|
||||
reset_parser();
|
||||
break;
|
||||
}
|
||||
|
||||
packet[index++] = b;
|
||||
if (index >= RADIO_PACKET_HEADER_LEN) {
|
||||
const uint16_t frame_len = read_le_u16(&packet[5]);
|
||||
const uint16_t offset = read_le_u16(&packet[7]);
|
||||
expected_audio_len = read_le_u16(&packet[9]);
|
||||
|
||||
if (packet[4] == 0 || packet[4] > MAX_AUDIO_CHUNKS ||
|
||||
packet[3] >= packet[4] ||
|
||||
frame_len == 0 || frame_len > AUDIO_MAX_PAYLOAD ||
|
||||
expected_audio_len == 0 || expected_audio_len > RADIO_AUDIO_CHUNK ||
|
||||
(uint32_t)offset + expected_audio_len > frame_len) {
|
||||
reset_parser();
|
||||
break;
|
||||
}
|
||||
|
||||
state = READ_AUDIO_BODY;
|
||||
}
|
||||
break;
|
||||
|
||||
case READ_AUDIO_BODY:
|
||||
if (index >= sizeof(packet)) {
|
||||
reset_parser();
|
||||
break;
|
||||
}
|
||||
|
||||
packet[index++] = b;
|
||||
if (index >= RADIO_PACKET_HEADER_LEN + expected_audio_len) {
|
||||
enqueue_audio_packet(packet, (uint16_t)index);
|
||||
reset_parser();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!read_anything) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
} else {
|
||||
taskYIELD();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool uart_write_all(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (data == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t sent = 0;
|
||||
const uint32_t started = millis();
|
||||
|
||||
while (sent < len) {
|
||||
const size_t written = PilotUart.write(data + sent, len - sent);
|
||||
sent += written;
|
||||
|
||||
if (sent >= len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((uint32_t)(millis() - started) >= PILOT_UART_WRITE_TIMEOUT_MS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delay(0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void reset_reassembly(uint8_t seq, uint8_t total_chunks, uint16_t frame_len, uint32_t epoch)
|
||||
{
|
||||
reassembly_active = true;
|
||||
reassembly_seq = seq;
|
||||
reassembly_total_chunks = total_chunks;
|
||||
reassembly_received_chunks = 0;
|
||||
reassembly_frame_len = frame_len;
|
||||
reassembly_epoch = epoch;
|
||||
reassembly_started_ms = millis();
|
||||
memset(chunk_received, 0, sizeof(chunk_received));
|
||||
}
|
||||
|
||||
bool uart_audio_init()
|
||||
{
|
||||
rx_packet_queue = xQueueCreate(
|
||||
PILOT_UART_PACKET_QUEUE_SIZE,
|
||||
sizeof(UartRxItem)
|
||||
);
|
||||
|
||||
if (rx_packet_queue == nullptr) {
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("ERROR: no se pudo crear la cola UART de audio");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debe configurarse antes de begin(), según la API HardwareSerial del ESP32.
|
||||
PilotUart.setRxBufferSize(PILOT_UART_RX_BUFFER_SIZE);
|
||||
PilotUart.begin(
|
||||
PILOT_UART_BAUD,
|
||||
SERIAL_8N1,
|
||||
PILOT_UART_RX_GPIO,
|
||||
PILOT_UART_TX_GPIO
|
||||
);
|
||||
|
||||
const BaseType_t task_result = xTaskCreatePinnedToCore(
|
||||
uart_rx_task,
|
||||
"pilot_uart_rx",
|
||||
PILOT_UART_TASK_STACK,
|
||||
nullptr,
|
||||
PILOT_UART_TASK_PRIORITY,
|
||||
&rx_task_handle,
|
||||
PILOT_UART_TASK_CORE
|
||||
);
|
||||
|
||||
if (task_result != pdPASS) {
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("ERROR: no se pudo crear la tarea UART");
|
||||
#endif
|
||||
rx_task_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.println("UART con intermediario iniciada");
|
||||
Serial.printf("Baudios: %d | RX GPIO%d | TX GPIO%d\n",
|
||||
PILOT_UART_BAUD,
|
||||
PILOT_UART_RX_GPIO,
|
||||
PILOT_UART_TX_GPIO);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uart_send_audio(uint8_t frame_seq, const int8_t *audio_data, uint16_t audio_len)
|
||||
{
|
||||
if (audio_data == nullptr || audio_len == 0 || audio_len > AUDIO_MAX_PAYLOAD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t total_chunks =
|
||||
(uint8_t)((audio_len + RADIO_AUDIO_CHUNK - 1) / RADIO_AUDIO_CHUNK);
|
||||
|
||||
for (uint8_t chunk_index = 0; chunk_index < total_chunks; ++chunk_index) {
|
||||
const uint16_t offset = (uint16_t)chunk_index * RADIO_AUDIO_CHUNK;
|
||||
const uint16_t remaining = (uint16_t)(audio_len - offset);
|
||||
const uint16_t chunk_len = remaining > RADIO_AUDIO_CHUNK
|
||||
? RADIO_AUDIO_CHUNK
|
||||
: remaining;
|
||||
|
||||
AudioWirePacket packet = {};
|
||||
packet.magic1 = RADIO_AUDIO_MAGIC_1;
|
||||
packet.magic2 = RADIO_AUDIO_MAGIC_2;
|
||||
packet.frame_seq = frame_seq;
|
||||
packet.chunk_index = chunk_index;
|
||||
packet.chunk_total = total_chunks;
|
||||
packet.frame_len = audio_len;
|
||||
packet.offset = offset;
|
||||
packet.chunk_len = chunk_len;
|
||||
memcpy(packet.audio, audio_data + offset, chunk_len);
|
||||
|
||||
const size_t packet_size = AUDIO_HEADER_SIZE + chunk_len;
|
||||
if (!uart_write_all(reinterpret_cast<const uint8_t *>(&packet), packet_size)) {
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
Serial.printf("ERROR UART enviando fragmento %u/%u\n",
|
||||
(unsigned)chunk_index,
|
||||
(unsigned)total_chunks);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void uart_discard_received_audio()
|
||||
{
|
||||
if (rx_packet_queue != nullptr) {
|
||||
UartRxItem discarded = {};
|
||||
while (xQueueReceive(rx_packet_queue, &discarded, 0) == pdTRUE) {
|
||||
}
|
||||
}
|
||||
|
||||
reassembly_active = false;
|
||||
reassembly_received_chunks = 0;
|
||||
memset(chunk_received, 0, sizeof(chunk_received));
|
||||
}
|
||||
|
||||
static bool finalize_box_stop_if_drained()
|
||||
{
|
||||
if (rx_packet_queue == nullptr || uxQueueMessagesWaiting(rx_packet_queue) != 0 || reassembly_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
if (box_stop_pending && box_ptt_active) {
|
||||
box_stop_pending = false;
|
||||
box_ptt_active = false;
|
||||
++box_stream_epoch;
|
||||
++box_control_version;
|
||||
changed = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool uart_box_ptt_active()
|
||||
{
|
||||
bool active = false;
|
||||
snapshot_control_state(&active, nullptr, nullptr, nullptr);
|
||||
return active;
|
||||
}
|
||||
|
||||
void uart_process_received_audio(bool reproduce)
|
||||
{
|
||||
static uint32_t processed_control_version = 0;
|
||||
|
||||
bool active = false;
|
||||
uint32_t current_epoch = 0;
|
||||
uint32_t current_version = 0;
|
||||
snapshot_control_state(&active, nullptr, ¤t_epoch, ¤t_version);
|
||||
|
||||
if (current_version != processed_control_version) {
|
||||
processed_control_version = current_version;
|
||||
reassembly_active = false;
|
||||
reassembly_received_chunks = 0;
|
||||
memset(chunk_received, 0, sizeof(chunk_received));
|
||||
|
||||
if (!active) {
|
||||
uart_discard_received_audio();
|
||||
// El último frame ya se escribió al I2S. Se solicita el cierre sin
|
||||
// vaciarlo, y el silencio final se añade tras el timeout normal.
|
||||
audio_request_playback_stop();
|
||||
} else if (reproduce) {
|
||||
audio_resume_playback();
|
||||
}
|
||||
}
|
||||
|
||||
if (!reproduce || !active) {
|
||||
uart_discard_received_audio();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reassembly_active &&
|
||||
(uint32_t)(millis() - reassembly_started_ms) > RADIO_REASSEMBLY_TIMEOUT_MS) {
|
||||
reassembly_active = false;
|
||||
}
|
||||
|
||||
UartRxItem item = {};
|
||||
while (rx_packet_queue != nullptr &&
|
||||
xQueueReceive(rx_packet_queue, &item, 0) == pdTRUE) {
|
||||
|
||||
snapshot_control_state(&active, nullptr, ¤t_epoch, nullptr);
|
||||
|
||||
if (!active || item.epoch != current_epoch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!audio_packet_valid(item.packet, item.packet_len)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioWirePacket packet = {};
|
||||
memcpy(&packet, item.packet, item.packet_len);
|
||||
|
||||
if (!reassembly_active ||
|
||||
reassembly_epoch != item.epoch ||
|
||||
packet.frame_seq != reassembly_seq ||
|
||||
packet.chunk_total != reassembly_total_chunks ||
|
||||
packet.frame_len != reassembly_frame_len) {
|
||||
reset_reassembly(
|
||||
packet.frame_seq,
|
||||
packet.chunk_total,
|
||||
packet.frame_len,
|
||||
item.epoch
|
||||
);
|
||||
}
|
||||
|
||||
if (!chunk_received[packet.chunk_index]) {
|
||||
memcpy(
|
||||
reassembly_buffer + packet.offset,
|
||||
packet.audio,
|
||||
packet.chunk_len
|
||||
);
|
||||
chunk_received[packet.chunk_index] = true;
|
||||
++reassembly_received_chunks;
|
||||
}
|
||||
|
||||
if (reassembly_received_chunks >= reassembly_total_chunks) {
|
||||
++debug_reassembled_frames;
|
||||
audio_play_frame_s8(reassembly_buffer, reassembly_frame_len);
|
||||
reassembly_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
// STOP se aplica únicamente cuando ya no queda ningún paquete ni un frame
|
||||
// incompleto. Así los auriculares reproducen el final completo del mensaje.
|
||||
if (finalize_box_stop_if_drained()) {
|
||||
audio_request_playback_stop();
|
||||
}
|
||||
}
|
||||
|
||||
void uart_audio_debug_report()
|
||||
{
|
||||
#if PILOT_DEBUG_SERIAL
|
||||
static uint32_t last_report_ms = 0;
|
||||
const uint32_t now = millis();
|
||||
|
||||
if ((uint32_t)(now - last_report_ms) < 1000) {
|
||||
return;
|
||||
}
|
||||
last_report_ms = now;
|
||||
|
||||
bool active = false;
|
||||
bool stop_pending = false;
|
||||
uint32_t epoch = 0;
|
||||
uint32_t version = 0;
|
||||
snapshot_control_state(&active, &stop_pending, &epoch, &version);
|
||||
|
||||
const UBaseType_t queued =
|
||||
rx_packet_queue != nullptr ? uxQueueMessagesWaiting(rx_packet_queue) : 0;
|
||||
|
||||
Serial.printf(
|
||||
"PILOTO BOX->INTERCOM: PTT=%s cierre_pendiente=%s ctrlON=%lu ctrlOFF=%lu paquetes=%lu frames=%lu autoSTART=%lu guard=%lu cola=%u drop=%lu epoch=%lu version=%lu\n",
|
||||
active ? "ON" : "OFF",
|
||||
stop_pending ? "SI" : "NO",
|
||||
(unsigned long)debug_control_start,
|
||||
(unsigned long)debug_control_stop,
|
||||
(unsigned long)debug_audio_packets,
|
||||
(unsigned long)debug_reassembled_frames,
|
||||
(unsigned long)debug_audio_autostarts,
|
||||
(unsigned long)debug_guard_drops,
|
||||
(unsigned int)queued,
|
||||
(unsigned long)dropped_uart_packets,
|
||||
(unsigned long)epoch,
|
||||
(unsigned long)version
|
||||
);
|
||||
#endif
|
||||
}
|
||||
10
Radio/Radio_piloto/uart_audio.hpp
Normal file
10
Radio/Radio_piloto/uart_audio.hpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
bool uart_audio_init();
|
||||
bool uart_send_audio(uint8_t frame_seq, const int8_t *audio_data, uint16_t audio_len);
|
||||
void uart_process_received_audio(bool reproduce);
|
||||
void uart_discard_received_audio();
|
||||
bool uart_box_ptt_active();
|
||||
void uart_audio_debug_report();
|
||||
Loading…
Add table
Add a link
Reference in a new issue