add a .clang-format file (#9154)

This commit is contained in:
Jorropo
2026-01-03 21:19:24 +01:00
committed by GitHub
parent abab6ce815
commit 0d11331d18
771 changed files with 77752 additions and 83184 deletions

View File

@@ -9,8 +9,8 @@
/*
AudioModule
A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project.
https://github.com/deulis/ESP32_Codec2
A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2
project. https://github.com/deulis/ESP32_Codec2
Codec 2 is a low-bitrate speech audio codec (speech coding)
that is patent free and open source develop by David Grant Rowe.
@@ -19,8 +19,8 @@
Basic Usage:
1) Enable the module by setting audio.codec2_enabled to 1.
2) Set the pins for the I2S interface. Recommended on TLora is I2S_WS 13/I2S_SD 15/I2S_SIN 2/I2S_SCK 14
3) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300,
CODEC2_1200, CODEC2_700, CODEC2_700B)
3) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400,
CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B)
KNOWN PROBLEMS
* Half Duplex
@@ -41,250 +41,238 @@ AudioModule *audioModule;
#include "graphics/ScreenFonts.h"
void run_codec2(void *parameter)
{
// 4 bytes of header in each frame hex c0 de c2 plus the bitrate
memcpy(audioModule->tx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header));
void run_codec2(void *parameter) {
// 4 bytes of header in each frame hex c0 de c2 plus the bitrate
memcpy(audioModule->tx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header));
LOG_INFO("Start codec2 task");
LOG_INFO("Start codec2 task");
while (true) {
uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000));
while (true) {
uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000));
if (tcount != 0) {
if (audioModule->radio_state == RadioState::tx) {
for (int i = 0; i < audioModule->adc_buffer_size; i++)
audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]);
if (tcount != 0) {
if (audioModule->radio_state == RadioState::tx) {
for (int i = 0; i < audioModule->adc_buffer_size; i++)
audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]);
codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index,
audioModule->speech);
audioModule->tx_encode_frame_index += audioModule->encode_codec_size;
codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, audioModule->speech);
audioModule->tx_encode_frame_index += audioModule->encode_codec_size;
if (audioModule->tx_encode_frame_index == (audioModule->encode_frame_size + sizeof(audioModule->tx_header))) {
LOG_INFO("Send %d codec2 bytes", audioModule->encode_frame_size);
audioModule->sendPayload();
audioModule->tx_encode_frame_index = sizeof(audioModule->tx_header);
}
}
if (audioModule->radio_state == RadioState::rx) {
size_t bytesOut = 0;
if (memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header)) == 0) {
for (int i = 4; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) {
codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut,
pdMS_TO_TICKS(500));
}
} else {
// if the buffer header does not match our own codec, make a temp decoding setup.
CODEC2 *tmp_codec2 = codec2_create(audioModule->rx_encode_frame[3]);
codec2_set_lpc_post_filter(tmp_codec2, 1, 0, 0.8, 0.2);
int tmp_encode_codec_size = (codec2_bits_per_frame(tmp_codec2) + 7) / 8;
int tmp_adc_buffer_size = codec2_samples_per_frame(tmp_codec2);
for (int i = 4; i < audioModule->rx_encode_frame_index; i += tmp_encode_codec_size) {
codec2_decode(tmp_codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
i2s_write(I2S_PORT, &audioModule->output_buffer, tmp_adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500));
}
codec2_destroy(tmp_codec2);
}
}
if (audioModule->tx_encode_frame_index == (audioModule->encode_frame_size + sizeof(audioModule->tx_header))) {
LOG_INFO("Send %d codec2 bytes", audioModule->encode_frame_size);
audioModule->sendPayload();
audioModule->tx_encode_frame_index = sizeof(audioModule->tx_header);
}
}
}
AudioModule::AudioModule() : SinglePortModule("Audio", meshtastic_PortNum_AUDIO_APP), concurrency::OSThread("Audio")
{
// moduleConfig.audio.codec2_enabled = true;
// moduleConfig.audio.i2s_ws = 13;
// moduleConfig.audio.i2s_sd = 15;
// moduleConfig.audio.i2s_din = 22;
// moduleConfig.audio.i2s_sck = 14;
// moduleConfig.audio.ptt_pin = 39;
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
LOG_INFO("Set up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
memcpy(tx_header.magic, c2_magic, sizeof(c2_magic));
tx_header.mode = (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1;
codec2_set_lpc_post_filter(codec2, 1, 0, 0.8, 0.2);
encode_codec_size = (codec2_bits_per_frame(codec2) + 7) / 8;
encode_frame_num = (meshtastic_Constants_DATA_PAYLOAD_LEN - sizeof(tx_header)) / encode_codec_size;
encode_frame_size = encode_frame_num * encode_codec_size; // max 233 bytes + 4 header bytes
adc_buffer_size = codec2_samples_per_frame(codec2);
LOG_INFO("Use %d frames of %d bytes for a total payload length of %d bytes", encode_frame_num, encode_codec_size,
encode_frame_size);
xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask);
} else {
disable();
}
}
void AudioModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
char buffer[50];
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
display->drawStringf(0 + x, 0 + y, buffer, "Codec2 Mode %d Audio",
(moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
display->setColor(WHITE);
display->setFont(FONT_LARGE);
display->setTextAlignment(TEXT_ALIGN_CENTER);
switch (radio_state) {
case RadioState::tx:
display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "PTT");
break;
default:
display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "Receive");
break;
}
}
int32_t AudioModule::runOnce()
{
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
esp_err_t res;
if (firstTime) {
// Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC
LOG_INFO("Init I2S SD: %d DIN: %d WS: %d SCK: %d", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din,
moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck);
i2s_config_t i2s_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) |
(moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)),
.sample_rate = 8000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = adc_buffer_size, // 320 * 2 bytes
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0};
res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
if (res != ESP_OK) {
LOG_ERROR("Failed to install I2S driver: %d", res);
}
const i2s_pin_config_t pin_config = {
.bck_io_num = moduleConfig.audio.i2s_sck,
.ws_io_num = moduleConfig.audio.i2s_ws,
.data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE,
.data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE};
res = i2s_set_pin(I2S_PORT, &pin_config);
if (res != ESP_OK) {
LOG_ERROR("Failed to set I2S pin config: %d", res);
}
res = i2s_start(I2S_PORT);
if (res != ESP_OK) {
LOG_ERROR("Failed to start I2S: %d", res);
}
radio_state = RadioState::rx;
// Configure PTT input
LOG_INFO("Init PTT on Pin %u", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN);
pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT);
firstTime = false;
}
if (audioModule->radio_state == RadioState::rx) {
size_t bytesOut = 0;
if (memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header)) == 0) {
for (int i = 4; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) {
codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500));
}
} else {
UIFrameEvent e;
// Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive.
if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) {
if (radio_state == RadioState::rx) {
LOG_INFO("PTT pressed, switching to TX");
radio_state = RadioState::tx;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
this->notifyObservers(&e);
}
} else {
if (radio_state == RadioState::tx) {
LOG_INFO("PTT released, switching to RX");
if (tx_encode_frame_index > sizeof(tx_header)) {
// Send the incomplete frame
LOG_INFO("Send %d codec2 bytes (incomplete)", tx_encode_frame_index);
sendPayload();
}
tx_encode_frame_index = sizeof(tx_header);
radio_state = RadioState::rx;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
this->notifyObservers(&e);
}
}
if (radio_state == RadioState::tx) {
// Get I2S data from the microphone and place in data buffer
size_t bytesIn = 0;
res = i2s_read(I2S_PORT, adc_buffer + adc_buffer_index, adc_buffer_size - adc_buffer_index, &bytesIn,
pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive.
if (res == ESP_OK) {
adc_buffer_index += bytesIn;
if (adc_buffer_index == adc_buffer_size) {
adc_buffer_index = 0;
memcpy((void *)speech, (void *)adc_buffer, 2 * adc_buffer_size);
// Notify run_codec2 task that the buffer is ready.
radio_state = RadioState::tx;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE)
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
// if the buffer header does not match our own codec, make a temp decoding setup.
CODEC2 *tmp_codec2 = codec2_create(audioModule->rx_encode_frame[3]);
codec2_set_lpc_post_filter(tmp_codec2, 1, 0, 0.8, 0.2);
int tmp_encode_codec_size = (codec2_bits_per_frame(tmp_codec2) + 7) / 8;
int tmp_adc_buffer_size = codec2_samples_per_frame(tmp_codec2);
for (int i = 4; i < audioModule->rx_encode_frame_index; i += tmp_encode_codec_size) {
codec2_decode(tmp_codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
i2s_write(I2S_PORT, &audioModule->output_buffer, tmp_adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500));
}
codec2_destroy(tmp_codec2);
}
return 100;
}
}
}
}
AudioModule::AudioModule() : SinglePortModule("Audio", meshtastic_PortNum_AUDIO_APP), concurrency::OSThread("Audio") {
// moduleConfig.audio.codec2_enabled = true;
// moduleConfig.audio.i2s_ws = 13;
// moduleConfig.audio.i2s_sd = 15;
// moduleConfig.audio.i2s_din = 22;
// moduleConfig.audio.i2s_sck = 14;
// moduleConfig.audio.ptt_pin = 39;
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
LOG_INFO("Set up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
memcpy(tx_header.magic, c2_magic, sizeof(c2_magic));
tx_header.mode = (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1;
codec2_set_lpc_post_filter(codec2, 1, 0, 0.8, 0.2);
encode_codec_size = (codec2_bits_per_frame(codec2) + 7) / 8;
encode_frame_num = (meshtastic_Constants_DATA_PAYLOAD_LEN - sizeof(tx_header)) / encode_codec_size;
encode_frame_size = encode_frame_num * encode_codec_size; // max 233 bytes + 4 header bytes
adc_buffer_size = codec2_samples_per_frame(codec2);
LOG_INFO("Use %d frames of %d bytes for a total payload length of %d bytes", encode_frame_num, encode_codec_size, encode_frame_size);
xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask);
} else {
disable();
}
}
void AudioModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
char buffer[50];
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
display->setColor(BLACK);
display->drawStringf(0 + x, 0 + y, buffer, "Codec2 Mode %d Audio",
(moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
display->setColor(WHITE);
display->setFont(FONT_LARGE);
display->setTextAlignment(TEXT_ALIGN_CENTER);
switch (radio_state) {
case RadioState::tx:
display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "PTT");
break;
default:
display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "Receive");
break;
}
}
int32_t AudioModule::runOnce() {
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
esp_err_t res;
if (firstTime) {
// Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC
LOG_INFO("Init I2S SD: %d DIN: %d WS: %d SCK: %d", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws,
moduleConfig.audio.i2s_sck);
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)),
.sample_rate = 8000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = adc_buffer_size, // 320 * 2 bytes
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0};
res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
if (res != ESP_OK) {
LOG_ERROR("Failed to install I2S driver: %d", res);
}
const i2s_pin_config_t pin_config = {.bck_io_num = moduleConfig.audio.i2s_sck,
.ws_io_num = moduleConfig.audio.i2s_ws,
.data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE,
.data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE};
res = i2s_set_pin(I2S_PORT, &pin_config);
if (res != ESP_OK) {
LOG_ERROR("Failed to set I2S pin config: %d", res);
}
res = i2s_start(I2S_PORT);
if (res != ESP_OK) {
LOG_ERROR("Failed to start I2S: %d", res);
}
radio_state = RadioState::rx;
// Configure PTT input
LOG_INFO("Init PTT on Pin %u", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN);
pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT);
firstTime = false;
} else {
return disable();
}
}
UIFrameEvent e;
// Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive.
if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) {
if (radio_state == RadioState::rx) {
LOG_INFO("PTT pressed, switching to TX");
radio_state = RadioState::tx;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
this->notifyObservers(&e);
}
} else {
if (radio_state == RadioState::tx) {
LOG_INFO("PTT released, switching to RX");
if (tx_encode_frame_index > sizeof(tx_header)) {
// Send the incomplete frame
LOG_INFO("Send %d codec2 bytes (incomplete)", tx_encode_frame_index);
sendPayload();
}
tx_encode_frame_index = sizeof(tx_header);
radio_state = RadioState::rx;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
this->notifyObservers(&e);
}
}
if (radio_state == RadioState::tx) {
// Get I2S data from the microphone and place in data buffer
size_t bytesIn = 0;
res = i2s_read(I2S_PORT, adc_buffer + adc_buffer_index, adc_buffer_size - adc_buffer_index, &bytesIn,
pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive.
meshtastic_MeshPacket *AudioModule::allocReply()
{
auto reply = allocDataPacket();
return reply;
}
bool AudioModule::shouldDraw()
{
if (!moduleConfig.audio.codec2_enabled) {
return false;
}
return (radio_state == RadioState::tx);
}
void AudioModule::sendPayload(NodeNum dest, bool wantReplies)
{
meshtastic_MeshPacket *p = allocReply();
p->to = dest;
p->decoded.want_response = wantReplies;
p->want_ack = false; // Audio is shoot&forget. No need to wait for ACKs.
p->priority = meshtastic_MeshPacket_Priority_MAX; // Audio is important, because realtime
p->decoded.payload.size = tx_encode_frame_index;
memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size);
service->sendToMesh(p);
}
ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
auto &p = mp.decoded;
if (!isFromUs(&mp)) {
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
radio_state = RadioState::rx;
rx_encode_frame_index = p.payload.size;
if (res == ESP_OK) {
adc_buffer_index += bytesIn;
if (adc_buffer_index == adc_buffer_size) {
adc_buffer_index = 0;
memcpy((void *)speech, (void *)adc_buffer, 2 * adc_buffer_size);
// Notify run_codec2 task that the buffer is ready.
radio_state = RadioState::tx;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE)
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
}
return 100;
} else {
return disable();
}
}
return ProcessMessage::CONTINUE;
meshtastic_MeshPacket *AudioModule::allocReply() {
auto reply = allocDataPacket();
return reply;
}
bool AudioModule::shouldDraw() {
if (!moduleConfig.audio.codec2_enabled) {
return false;
}
return (radio_state == RadioState::tx);
}
void AudioModule::sendPayload(NodeNum dest, bool wantReplies) {
meshtastic_MeshPacket *p = allocReply();
p->to = dest;
p->decoded.want_response = wantReplies;
p->want_ack = false; // Audio is shoot&forget. No need to wait for ACKs.
p->priority = meshtastic_MeshPacket_Priority_MAX; // Audio is important, because realtime
p->decoded.payload.size = tx_encode_frame_index;
memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size);
service->sendToMesh(p);
}
ProcessMessage AudioModule::handleReceived(const meshtastic_MeshPacket &mp) {
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
auto &p = mp.decoded;
if (!isFromUs(&mp)) {
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
radio_state = RadioState::rx;
rx_encode_frame_index = p.payload.size;
// Notify run_codec2 task that the buffer is ready.
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE)
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
return ProcessMessage::CONTINUE;
}
#endif

View File

@@ -18,8 +18,8 @@ enum RadioState { standby, rx, tx };
const char c2_magic[3] = {0xc0, 0xde, 0xc2}; // Magic number for codec2 header
struct c2_header {
char magic[3];
char mode;
char magic[3];
char mode;
};
#define ADC_BUFFER_SIZE_MAX 320
@@ -30,56 +30,55 @@ struct c2_header {
#define AUDIO_MODULE_RX_BUFFER 128
#define AUDIO_MODULE_MODE meshtastic_ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700
class AudioModule : public SinglePortModule, public Observable<const UIFrameEvent *>, private concurrency::OSThread
{
public:
unsigned char rx_encode_frame[meshtastic_Constants_DATA_PAYLOAD_LEN] = {};
unsigned char tx_encode_frame[meshtastic_Constants_DATA_PAYLOAD_LEN] = {};
c2_header tx_header = {};
int16_t speech[ADC_BUFFER_SIZE_MAX] = {};
int16_t output_buffer[ADC_BUFFER_SIZE_MAX] = {};
uint16_t adc_buffer[ADC_BUFFER_SIZE_MAX] = {};
int adc_buffer_size = 0;
uint16_t adc_buffer_index = 0;
int tx_encode_frame_index = sizeof(c2_header); // leave room for header
int rx_encode_frame_index = 0;
int encode_codec_size = 0;
int encode_frame_size = 0;
volatile RadioState radio_state = RadioState::rx;
class AudioModule : public SinglePortModule, public Observable<const UIFrameEvent *>, private concurrency::OSThread {
public:
unsigned char rx_encode_frame[meshtastic_Constants_DATA_PAYLOAD_LEN] = {};
unsigned char tx_encode_frame[meshtastic_Constants_DATA_PAYLOAD_LEN] = {};
c2_header tx_header = {};
int16_t speech[ADC_BUFFER_SIZE_MAX] = {};
int16_t output_buffer[ADC_BUFFER_SIZE_MAX] = {};
uint16_t adc_buffer[ADC_BUFFER_SIZE_MAX] = {};
int adc_buffer_size = 0;
uint16_t adc_buffer_index = 0;
int tx_encode_frame_index = sizeof(c2_header); // leave room for header
int rx_encode_frame_index = 0;
int encode_codec_size = 0;
int encode_frame_size = 0;
volatile RadioState radio_state = RadioState::rx;
struct CODEC2 *codec2 = NULL;
// int16_t sample;
struct CODEC2 *codec2 = NULL;
// int16_t sample;
AudioModule();
AudioModule();
bool shouldDraw();
bool shouldDraw();
/**
* Send our payload into the mesh
*/
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
/**
* Send our payload into the mesh
*/
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
protected:
int encode_frame_num = 0;
bool firstTime = true;
protected:
int encode_frame_num = 0;
bool firstTime = true;
virtual int32_t runOnce() override;
virtual int32_t runOnce() override;
virtual meshtastic_MeshPacket *allocReply() override;
virtual meshtastic_MeshPacket *allocReply() override;
virtual bool wantUIFrame() override { return this->shouldDraw(); }
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
virtual bool wantUIFrame() override { return this->shouldDraw(); }
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
#if !HAS_SCREEN
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
#else
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
#endif
/** Called to handle a particular incoming message
* @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered
* for it
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
/** Called to handle a particular incoming message
* @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be
* considered for it
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
};
extern AudioModule *audioModule;

View File

@@ -15,20 +15,16 @@ PaxcounterModule *paxcounterModule;
* We only clear our sent flag here, since this function is called from another thread, so we
* cannot send to the mesh directly.
*/
void PaxcounterModule::handlePaxCounterReportRequest()
{
// The libpax library already updated our data structure, just before invoking this callback.
LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu",
paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000);
paxcounterModule->reportedDataSent = false;
paxcounterModule->setIntervalFromNow(0);
void PaxcounterModule::handlePaxCounterReportRequest() {
// The libpax library already updated our data structure, just before invoking this callback.
LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu", paxcounterModule->count_from_libpax.wifi_count,
paxcounterModule->count_from_libpax.ble_count, millis() / 1000);
paxcounterModule->reportedDataSent = false;
paxcounterModule->setIntervalFromNow(0);
}
PaxcounterModule::PaxcounterModule()
: concurrency::OSThread("Paxcounter"),
ProtobufModule("paxcounter", meshtastic_PortNum_PAXCOUNTER_APP, &meshtastic_Paxcount_msg)
{
}
: concurrency::OSThread("Paxcounter"), ProtobufModule("paxcounter", meshtastic_PortNum_PAXCOUNTER_APP, &meshtastic_Paxcount_msg) {}
/**
* Send the Pax information to the mesh if we got new data from libpax.
@@ -37,79 +33,72 @@ PaxcounterModule::PaxcounterModule()
* @param dest - destination node (usually NODENUM_BROADCAST)
* @return false if sending is unnecessary, true if information was sent
*/
bool PaxcounterModule::sendInfo(NodeNum dest)
{
if (paxcounterModule->reportedDataSent)
return false;
bool PaxcounterModule::sendInfo(NodeNum dest) {
if (paxcounterModule->reportedDataSent)
return false;
LOG_INFO("PaxcounterModule: send pax info wifi=%d; ble=%d; uptime=%lu", count_from_libpax.wifi_count,
count_from_libpax.ble_count, millis() / 1000);
LOG_INFO("PaxcounterModule: send pax info wifi=%d; ble=%d; uptime=%lu", count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000);
meshtastic_Paxcount pl = meshtastic_Paxcount_init_default;
pl.wifi = count_from_libpax.wifi_count;
pl.ble = count_from_libpax.ble_count;
pl.uptime = millis() / 1000;
meshtastic_Paxcount pl = meshtastic_Paxcount_init_default;
pl.wifi = count_from_libpax.wifi_count;
pl.ble = count_from_libpax.ble_count;
pl.uptime = millis() / 1000;
meshtastic_MeshPacket *p = allocDataProtobuf(pl);
p->to = dest;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
meshtastic_MeshPacket *p = allocDataProtobuf(pl);
p->to = dest;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
service->sendToMesh(p, RX_SRC_LOCAL, true);
service->sendToMesh(p, RX_SRC_LOCAL, true);
paxcounterModule->reportedDataSent = true;
paxcounterModule->reportedDataSent = true;
return true;
return true;
}
bool PaxcounterModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Paxcount *p)
{
return false; // Let others look at this message also if they want. We don't do anything with received packets.
bool PaxcounterModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Paxcount *p) {
return false; // Let others look at this message also if they want. We don't do anything with received packets.
}
meshtastic_MeshPacket *PaxcounterModule::allocReply()
{
meshtastic_Paxcount pl = meshtastic_Paxcount_init_default;
pl.wifi = count_from_libpax.wifi_count;
pl.ble = count_from_libpax.ble_count;
pl.uptime = millis() / 1000;
return allocDataProtobuf(pl);
meshtastic_MeshPacket *PaxcounterModule::allocReply() {
meshtastic_Paxcount pl = meshtastic_Paxcount_init_default;
pl.wifi = count_from_libpax.wifi_count;
pl.ble = count_from_libpax.ble_count;
pl.uptime = millis() / 1000;
return allocDataProtobuf(pl);
}
int32_t PaxcounterModule::runOnce()
{
if (isActive()) {
if (firstTime) {
firstTime = false;
LOG_DEBUG("Paxcounter starting up with interval of %d seconds",
Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval,
default_telemetry_broadcast_interval_secs));
struct libpax_config_t configuration;
libpax_default_config(&configuration);
int32_t PaxcounterModule::runOnce() {
if (isActive()) {
if (firstTime) {
firstTime = false;
LOG_DEBUG("Paxcounter starting up with interval of %d seconds",
Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval, default_telemetry_broadcast_interval_secs));
struct libpax_config_t configuration;
libpax_default_config(&configuration);
configuration.blecounter = 1;
configuration.blescantime = 0; // infinite
configuration.wificounter = 1;
configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
configuration.wifi_channel_switch_interval = 50;
configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80);
configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80);
libpax_update_config(&configuration);
configuration.blecounter = 1;
configuration.blescantime = 0; // infinite
configuration.wificounter = 1;
configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
configuration.wifi_channel_switch_interval = 50;
configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80);
configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80);
libpax_update_config(&configuration);
// internal processing initialization
libpax_counter_init(handlePaxCounterReportRequest, &count_from_libpax,
Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval,
default_telemetry_broadcast_interval_secs),
0);
libpax_counter_start();
} else {
sendInfo(NODENUM_BROADCAST);
}
return Default::getConfiguredOrDefaultMsScaled(moduleConfig.paxcounter.paxcounter_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes);
// internal processing initialization
libpax_counter_init(
handlePaxCounterReportRequest, &count_from_libpax,
Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval, default_telemetry_broadcast_interval_secs), 0);
libpax_counter_start();
} else {
return disable();
sendInfo(NODENUM_BROADCAST);
}
return Default::getConfiguredOrDefaultMsScaled(moduleConfig.paxcounter.paxcounter_update_interval, default_telemetry_broadcast_interval_secs,
numOnlineNodes);
} else {
return disable();
}
}
#if HAS_SCREEN
@@ -117,31 +106,29 @@ int32_t PaxcounterModule::runOnce()
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
// === Set Title
const char *titleStr = "Pax";
// === Set Title
const char *titleStr = "Pax";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
char buffer[50];
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
char buffer[50];
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
libpax_counter_count(&count_from_libpax);
libpax_counter_count(&count_from_libpax);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_SMALL);
display->drawStringf(display->getWidth() / 2 + x, graphics::getTextPositions(display)[line++], buffer,
"WiFi: %d\nBLE: %d\nUptime: %ds", count_from_libpax.wifi_count, count_from_libpax.ble_count,
millis() / 1000);
graphics::drawCommonFooter(display, x, y);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_SMALL);
display->drawStringf(display->getWidth() / 2 + x, graphics::getTextPositions(display)[line++], buffer, "WiFi: %d\nBLE: %d\nUptime: %ds",
count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000);
graphics::drawCommonFooter(display, x, y);
}
#endif // HAS_SCREEN

View File

@@ -11,26 +11,25 @@
* Wrapper module for the estimate passenger (PAX) count library (https://github.com/dbinfrago/libpax) which
* implements the core functionality of the ESP32 Paxcounter project (https://github.com/cyberman54/ESP32-Paxcounter)
*/
class PaxcounterModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Paxcount>
{
bool firstTime = true;
bool reportedDataSent = true;
class PaxcounterModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Paxcount> {
bool firstTime = true;
bool reportedDataSent = true;
static void handlePaxCounterReportRequest();
static void handlePaxCounterReportRequest();
public:
PaxcounterModule();
public:
PaxcounterModule();
protected:
struct count_payload_t count_from_libpax = {0, 0, 0};
virtual int32_t runOnce() override;
bool sendInfo(NodeNum dest = NODENUM_BROADCAST);
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Paxcount *p) override;
virtual meshtastic_MeshPacket *allocReply() override;
bool isActive() { return moduleConfig.paxcounter.enabled && !config.bluetooth.enabled && !config.network.wifi_enabled; }
protected:
struct count_payload_t count_from_libpax = {0, 0, 0};
virtual int32_t runOnce() override;
bool sendInfo(NodeNum dest = NODENUM_BROADCAST);
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Paxcount *p) override;
virtual meshtastic_MeshPacket *allocReply() override;
bool isActive() { return moduleConfig.paxcounter.enabled && !config.bluetooth.enabled && !config.network.wifi_enabled; }
#if HAS_SCREEN
virtual bool wantUIFrame() override { return isActive(); }
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
virtual bool wantUIFrame() override { return isActive(); }
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
#endif
};