fix(reticulum): close Pager audio and LXST call paths

This commit is contained in:
liu weikai committed 2026-07-15 01:08:15 +08:00
1 parent e328238666
commit 80fcaf5088
7 files changed
+558 -129

No files matched your search

@@ -70,6 +70,16 @@ class SX1262Access : public SX1262
namespace boards::tlora_pager
{
enum class PagerAudioOwner : uint8_t
{
None,
MessageTone,
IncomingCallTone,
ReticulumCall,
Walkie,
Sstv,
};
/**
* @class TLoRaPagerBoard
* @brief Main board class for T-LoRa-Pager hardware
@@ -235,6 +245,17 @@ class TLoRaPagerBoard : public BoardBase,
void setMessageToneVolume(uint8_t volume_percent) override;
uint8_t getMessageToneVolume() const override;
int openAudioSession(PagerAudioOwner owner, uint8_t bits_per_sample,
uint8_t channels, uint32_t sample_rate,
bool speaker_enabled);
void closeAudioSession(PagerAudioOwner owner);
int audioRead(PagerAudioOwner owner, uint8_t* buffer, size_t size);
int audioWrite(PagerAudioOwner owner, const uint8_t* buffer, size_t size);
bool audioSetVolume(PagerAudioOwner owner, uint8_t level);
bool audioSetGain(PagerAudioOwner owner, float db_value);
bool audioSetMute(PagerAudioOwner owner, bool enabled);
bool audioSetOutMute(PagerAudioOwner owner, bool enabled);
/**
* @brief Set haptic effect waveform
* @param effects Effect number (0-127, see DRV2605 documentation)
+397 -55
View File
@@ -8,7 +8,9 @@
#include "freertos/task.h"
#include "freertos/timers.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <driver/gpio.h>
#include <esp_heap_caps.h>
#include <esp_sleep.h>
@@ -76,6 +78,82 @@ static int s_gauge_error_streak = 0;
static int s_gauge_reinit_count = 0;
static uint32_t s_last_radio_spi_lock_timeout_log_ms = 0;
static uint32_t s_suppressed_radio_spi_lock_timeout_logs = 0;
static portMUX_TYPE s_audio_owner_mux = portMUX_INITIALIZER_UNLOCKED;
static PagerAudioOwner s_audio_owner = PagerAudioOwner::None;
static uint8_t s_audio_logical_channels = 0;
static constexpr size_t kAudioConversionFrames = 256;
static int16_t s_audio_read_scratch[kAudioConversionFrames * 2] = {};
static int16_t s_audio_write_scratch[kAudioConversionFrames * 2] = {};
const char* audioOwnerLabel(PagerAudioOwner owner)
{
switch (owner)
{
case PagerAudioOwner::MessageTone:
return "message_tone";
case PagerAudioOwner::IncomingCallTone:
return "incoming_call_tone";
case PagerAudioOwner::ReticulumCall:
return "reticulum_call";
case PagerAudioOwner::Walkie:
return "walkie";
case PagerAudioOwner::Sstv:
return "sstv";
case PagerAudioOwner::None:
default:
return "none";
}
}
PagerAudioOwner currentAudioOwner()
{
portENTER_CRITICAL(&s_audio_owner_mux);
const PagerAudioOwner owner = s_audio_owner;
portEXIT_CRITICAL(&s_audio_owner_mux);
return owner;
}
bool claimAudioOwner(PagerAudioOwner owner, uint8_t logical_channels)
{
if (owner == PagerAudioOwner::None || logical_channels == 0)
{
return false;
}
portENTER_CRITICAL(&s_audio_owner_mux);
const bool claimed = s_audio_owner == PagerAudioOwner::None;
if (claimed)
{
s_audio_owner = owner;
s_audio_logical_channels = logical_channels;
}
portEXIT_CRITICAL(&s_audio_owner_mux);
return claimed;
}
bool audioOwnerMatches(PagerAudioOwner owner)
{
return owner != PagerAudioOwner::None && currentAudioOwner() == owner;
}
uint8_t audioLogicalChannels(PagerAudioOwner owner)
{
portENTER_CRITICAL(&s_audio_owner_mux);
const uint8_t channels = s_audio_owner == owner ? s_audio_logical_channels : 0;
portEXIT_CRITICAL(&s_audio_owner_mux);
return channels;
}
void releaseAudioOwner(PagerAudioOwner owner)
{
portENTER_CRITICAL(&s_audio_owner_mux);
if (s_audio_owner == owner)
{
s_audio_owner = PagerAudioOwner::None;
s_audio_logical_channels = 0;
}
portEXIT_CRITICAL(&s_audio_owner_mux);
}
// Temperature state (from BQ27220), used for safety / UI hints / brightness caps.
static float s_last_temp_c = NAN;
@@ -590,13 +668,9 @@ uint32_t TLoRaPagerBoard::begin(uint32_t disable_hw_init)
if (codec.begin(Wire, 0x18, CODEC_TYPE_ES8311))
{
devices_probe |= HW_CODEC_ONLINE;
powerControl(POWER_SPEAK, false);
log_d("Audio codec (ES8311) initialized successfully");
Serial.printf("[TLoRaPagerBoard::begin] audio codec init end ok=1\n");
// Set power amplifier control callback
codec.setPaPinCallback([](bool enable, void* user_data)
{ ((ExtensionIOXL9555*)user_data)->digitalWrite(EXPANDS_AMP_EN, enable); },
&io);
}
else
{
@@ -1101,6 +1175,268 @@ void TLoRaPagerBoard::stopVibrator()
log_d("[stopVibrator] Power disabled, function completed");
}
int TLoRaPagerBoard::openAudioSession(PagerAudioOwner owner, uint8_t bits_per_sample,
uint8_t channels, uint32_t sample_rate,
bool speaker_enabled)
{
#ifndef USING_AUDIO_CODEC
(void)owner;
(void)bits_per_sample;
(void)channels;
(void)sample_rate;
(void)speaker_enabled;
return -1;
#else
if (!(devices_probe & HW_CODEC_ONLINE) || owner == PagerAudioOwner::None)
{
return -1;
}
if (!claimAudioOwner(owner, channels))
{
Serial.printf("[Audio][Pager] busy requested=%s owner=%s\n",
audioOwnerLabel(owner),
audioOwnerLabel(currentAudioOwner()));
return -2;
}
powerControl(POWER_SPEAK, false);
const uint8_t hardware_channels = channels == 1 ? 2 : channels;
int state = -1;
{
I2CGuard i2c;
state = codec.open(bits_per_sample, hardware_channels, sample_rate);
if (state == 0)
{
codec.setMute(true);
codec.setOutMute(true);
}
}
if (state != 0)
{
releaseAudioOwner(owner);
Serial.printf("[Audio][Pager] open failed owner=%s rc=%d rate=%lu logical_ch=%u hw_ch=%u\n",
audioOwnerLabel(owner),
state,
static_cast<unsigned long>(sample_rate),
static_cast<unsigned>(channels),
static_cast<unsigned>(hardware_channels));
return state;
}
if (speaker_enabled)
{
powerControl(POWER_SPEAK, true);
delay(10);
}
Serial.printf("[Audio][Pager] open owner=%s rate=%lu logical_ch=%u hw_ch=%u speaker=%u\n",
audioOwnerLabel(owner),
static_cast<unsigned long>(sample_rate),
static_cast<unsigned>(channels),
static_cast<unsigned>(hardware_channels),
speaker_enabled ? 1U : 0U);
return 0;
#endif
}
void TLoRaPagerBoard::closeAudioSession(PagerAudioOwner owner)
{
#ifdef USING_AUDIO_CODEC
if (!audioOwnerMatches(owner))
{
return;
}
{
I2CGuard i2c;
codec.setMute(true);
codec.setOutMute(true);
}
powerControl(POWER_SPEAK, false);
{
I2CGuard i2c;
codec.close();
}
releaseAudioOwner(owner);
Serial.printf("[Audio][Pager] close owner=%s\n", audioOwnerLabel(owner));
#else
(void)owner;
#endif
}
int TLoRaPagerBoard::audioRead(PagerAudioOwner owner, uint8_t* buffer, size_t size)
{
#ifdef USING_AUDIO_CODEC
const uint8_t logical_channels = audioLogicalChannels(owner);
if (!buffer || size == 0 || logical_channels == 0)
{
return -1;
}
if (logical_channels != 1)
{
return codec.read(buffer, size);
}
if ((size % sizeof(int16_t)) != 0)
{
return -1;
}
auto* mono = reinterpret_cast<int16_t*>(buffer);
const size_t frame_count = size / sizeof(int16_t);
size_t frame_offset = 0;
while (frame_offset < frame_count)
{
const size_t chunk_frames =
std::min(kAudioConversionFrames, frame_count - frame_offset);
const int state = codec.read(
reinterpret_cast<uint8_t*>(s_audio_read_scratch),
chunk_frames * 2 * sizeof(int16_t));
if (state != 0)
{
return state;
}
int64_t left_energy = 0;
int64_t right_energy = 0;
for (size_t index = 0; index < chunk_frames; ++index)
{
const int32_t left = s_audio_read_scratch[index * 2];
const int32_t right = s_audio_read_scratch[(index * 2) + 1];
left_energy += left < 0 ? -left : left;
right_energy += right < 0 ? -right : right;
}
const size_t active_channel = right_energy > left_energy ? 1 : 0;
for (size_t index = 0; index < chunk_frames; ++index)
{
mono[frame_offset + index] =
s_audio_read_scratch[(index * 2) + active_channel];
}
frame_offset += chunk_frames;
}
return 0;
#else
(void)owner;
(void)buffer;
(void)size;
return -1;
#endif
}
int TLoRaPagerBoard::audioWrite(PagerAudioOwner owner, const uint8_t* buffer, size_t size)
{
#ifdef USING_AUDIO_CODEC
const uint8_t logical_channels = audioLogicalChannels(owner);
if (!buffer || size == 0 || logical_channels == 0)
{
return -1;
}
if (logical_channels != 1)
{
return codec.write(const_cast<uint8_t*>(buffer), size);
}
if ((size % sizeof(int16_t)) != 0)
{
return -1;
}
const auto* mono = reinterpret_cast<const int16_t*>(buffer);
const size_t frame_count = size / sizeof(int16_t);
size_t frame_offset = 0;
while (frame_offset < frame_count)
{
const size_t chunk_frames =
std::min(kAudioConversionFrames, frame_count - frame_offset);
for (size_t index = 0; index < chunk_frames; ++index)
{
const int16_t sample = mono[frame_offset + index];
s_audio_write_scratch[index * 2] = sample;
s_audio_write_scratch[(index * 2) + 1] = sample;
}
const int state = codec.write(
reinterpret_cast<uint8_t*>(s_audio_write_scratch),
chunk_frames * 2 * sizeof(int16_t));
if (state != 0)
{
return state;
}
frame_offset += chunk_frames;
}
return 0;
#else
(void)owner;
(void)buffer;
(void)size;
return -1;
#endif
}
bool TLoRaPagerBoard::audioSetVolume(PagerAudioOwner owner, uint8_t level)
{
#ifdef USING_AUDIO_CODEC
if (!audioOwnerMatches(owner))
{
return false;
}
I2CGuard i2c;
codec.setVolume(level);
return true;
#else
(void)owner;
(void)level;
return false;
#endif
}
bool TLoRaPagerBoard::audioSetGain(PagerAudioOwner owner, float db_value)
{
#ifdef USING_AUDIO_CODEC
if (!audioOwnerMatches(owner))
{
return false;
}
I2CGuard i2c;
codec.setGain(db_value);
return true;
#else
(void)owner;
(void)db_value;
return false;
#endif
}
bool TLoRaPagerBoard::audioSetMute(PagerAudioOwner owner, bool enabled)
{
#ifdef USING_AUDIO_CODEC
if (!audioOwnerMatches(owner))
{
return false;
}
I2CGuard i2c;
codec.setMute(enabled);
return true;
#else
(void)owner;
(void)enabled;
return false;
#endif
}
bool TLoRaPagerBoard::audioSetOutMute(PagerAudioOwner owner, bool enabled)
{
#ifdef USING_AUDIO_CODEC
if (!audioOwnerMatches(owner))
{
return false;
}
I2CGuard i2c;
codec.setOutMute(enabled);
return true;
#else
(void)owner;
(void)enabled;
return false;
#endif
}
void TLoRaPagerBoard::playMessageTone()
{
#ifndef USING_AUDIO_CODEC
@@ -1135,42 +1471,43 @@ void TLoRaPagerBoard::playMessageTone()
static constexpr size_t kFramesPerChunk = 128;
namespace pager_tone = ::platform::ui::audio::pager_notification;
int prev_volume = codec.getVolume();
if (prev_volume < 0 || prev_volume > 100)
{
prev_volume = 50;
}
bool prev_out_mute = codec.getOutMute();
const int open_result =
codec.open(16, pager_tone::kChannels, pager_tone::kPlaybackSampleRateHz);
constexpr PagerAudioOwner kOwner = PagerAudioOwner::MessageTone;
const int open_result = openAudioSession(
kOwner, 16, 1, pager_tone::kPlaybackSampleRateHz, true);
if (open_result != 0)
{
Serial.printf("[Audio][PagerTone] open failed kind=message rc=%d rate=%lu channels=%u\n",
Serial.printf("[Audio][PagerTone] open failed kind=message rc=%d rate=%lu channels=1\n",
open_result,
static_cast<unsigned long>(pager_tone::kPlaybackSampleRateHz),
static_cast<unsigned>(pager_tone::kChannels));
static_cast<unsigned long>(pager_tone::kPlaybackSampleRateHz));
s_playing = false;
return;
}
codec.setOutMute(false);
codec.setVolume(_message_tone_volume);
delay(10);
if (!audioSetMute(kOwner, true) ||
!audioSetVolume(kOwner, _message_tone_volume) ||
!audioSetOutMute(kOwner, false))
{
closeAudioSession(kOwner);
s_playing = false;
return;
}
int16_t pcm[kFramesPerChunk * pager_tone::kChannels];
int16_t pcm[kFramesPerChunk];
pager_tone::AdpcmPlaybackState tone_state{};
while (pager_tone::hasMore(tone_state))
{
const uint16_t frames = pager_tone::fillStereoInterleaved(
tone_state, pcm, static_cast<uint16_t>(kFramesPerChunk));
uint16_t frames = 0;
while (frames < kFramesPerChunk &&
pager_tone::nextPlaybackSample(tone_state, pcm[frames]))
{
++frames;
}
if (frames == 0)
{
break;
}
const int write_result = codec.write(reinterpret_cast<uint8_t*>(pcm),
frames * pager_tone::kChannels *
sizeof(int16_t));
const int write_result = audioWrite(
kOwner, reinterpret_cast<const uint8_t*>(pcm), frames * sizeof(int16_t));
if (write_result != 0)
{
Serial.printf("[Audio][PagerTone] write failed kind=message rc=%d frames=%u\n",
@@ -1178,12 +1515,14 @@ void TLoRaPagerBoard::playMessageTone()
static_cast<unsigned>(frames));
break;
}
delay(1);
}
delay(5);
codec.setVolume(static_cast<uint8_t>(prev_volume));
codec.setOutMute(prev_out_mute);
codec.close();
memset(pcm, 0, sizeof(pcm));
(void)audioWrite(kOwner, reinterpret_cast<const uint8_t*>(pcm), sizeof(pcm));
(void)audioWrite(kOwner, reinterpret_cast<const uint8_t*>(pcm), sizeof(pcm));
delay(7);
closeAudioSession(kOwner);
s_playing = false;
#endif
}
@@ -1222,30 +1561,28 @@ void TLoRaPagerBoard::playIncomingCallTone(const volatile bool* stop_requested)
static constexpr size_t kFramesPerChunk = 128;
namespace call_tone = ::platform::ui::audio::call_notification;
int prev_volume = codec.getVolume();
if (prev_volume < 0 || prev_volume > 100)
{
prev_volume = 50;
}
bool prev_out_mute = codec.getOutMute();
const int open_result =
codec.open(16, call_tone::kChannels, call_tone::kPlaybackSampleRateHz);
constexpr PagerAudioOwner kOwner = PagerAudioOwner::IncomingCallTone;
const int open_result = openAudioSession(
kOwner, 16, 1, call_tone::kPlaybackSampleRateHz, true);
if (open_result != 0)
{
Serial.printf("[Audio][PagerTone] open failed kind=call rc=%d rate=%lu channels=%u\n",
Serial.printf("[Audio][PagerTone] open failed kind=call rc=%d rate=%lu channels=1\n",
open_result,
static_cast<unsigned long>(call_tone::kPlaybackSampleRateHz),
static_cast<unsigned>(call_tone::kChannels));
static_cast<unsigned long>(call_tone::kPlaybackSampleRateHz));
s_playing = false;
return;
}
codec.setOutMute(false);
codec.setVolume(_message_tone_volume);
delay(10);
if (!audioSetMute(kOwner, true) ||
!audioSetVolume(kOwner, _message_tone_volume) ||
!audioSetOutMute(kOwner, false))
{
closeAudioSession(kOwner);
s_playing = false;
return;
}
int16_t pcm[kFramesPerChunk * call_tone::kChannels];
int16_t pcm[kFramesPerChunk];
call_tone::AdpcmPlaybackState tone_state{};
while (call_tone::hasMore(tone_state))
{
@@ -1253,15 +1590,18 @@ void TLoRaPagerBoard::playIncomingCallTone(const volatile bool* stop_requested)
{
break;
}
const uint16_t frames = call_tone::fillStereoInterleaved(
tone_state, pcm, static_cast<uint16_t>(kFramesPerChunk));
uint16_t frames = 0;
while (frames < kFramesPerChunk &&
call_tone::nextPlaybackSample(tone_state, pcm[frames]))
{
++frames;
}
if (frames == 0)
{
break;
}
const int write_result = codec.write(reinterpret_cast<uint8_t*>(pcm),
frames * call_tone::kChannels *
sizeof(int16_t));
const int write_result = audioWrite(
kOwner, reinterpret_cast<const uint8_t*>(pcm), frames * sizeof(int16_t));
if (write_result != 0)
{
Serial.printf("[Audio][PagerTone] write failed kind=call rc=%d frames=%u\n",
@@ -1269,12 +1609,14 @@ void TLoRaPagerBoard::playIncomingCallTone(const volatile bool* stop_requested)
static_cast<unsigned>(frames));
break;
}
delay(1);
}
delay(5);
codec.setVolume(static_cast<uint8_t>(prev_volume));
codec.setOutMute(prev_out_mute);
codec.close();
memset(pcm, 0, sizeof(pcm));
(void)audioWrite(kOwner, reinterpret_cast<const uint8_t*>(pcm), sizeof(pcm));
(void)audioWrite(kOwner, reinterpret_cast<const uint8_t*>(pcm), sizeof(pcm));
delay(7);
closeAudioSession(kOwner);
s_playing = false;
#endif
}
@@ -3930,14 +3930,21 @@ bool LxmfAdapter::handleLinkRequestPacket(
return false;
}
if (packet.transport_id &&
!hashesEqual(packet.transport_id, identity_.identityHash(), reticulum::kTruncatedHashSize))
LocalDestinationKind local_kind = LocalDestinationKind::Delivery;
const bool local_destination =
isLocalDestinationHash(packet.destination_hash, &local_kind);
const bool wifi_final_delivery =
local_destination &&
ingress_interface == reticulum::interfaces::InterfaceKind::WifiGateway;
if (packet.transport_id && !wifi_final_delivery &&
!hashesEqual(packet.transport_id,
identity_.identityHash(),
reticulum::kTruncatedHashSize))
{
return false;
}
LocalDestinationKind local_kind = LocalDestinationKind::Delivery;
if (isLocalDestinationHash(packet.destination_hash, &local_kind))
if (local_destination)
{
if (local_kind == LocalDestinationKind::CallAudio &&
ingress_interface != reticulum::interfaces::InterfaceKind::WifiGateway)
@@ -19,14 +19,15 @@ namespace
{
constexpr uint8_t kBitsPerSample = 16;
constexpr uint8_t kCodecChannels = 2;
constexpr uint8_t kCodecChannels = 1;
constexpr std::size_t kMaxCodec2SamplesPerFrame = 320;
constexpr uint8_t kDefaultVolume = 78;
constexpr float kDefaultGainDb = 36.0f;
constexpr auto kAudioOwner =
::boards::tlora_pager::PagerAudioOwner::ReticulumCall;
bool s_registered = false;
::boards::tlora_pager::TLoRaPagerBoard* s_board = nullptr;
int16_t s_codec_pcm[kMaxCodec2SamplesPerFrame * kCodecChannels] = {};
::boards::tlora_pager::TLoRaPagerBoard* resolve_board()
{
@@ -49,16 +50,26 @@ bool open_audio(uint32_t sample_rate_hz)
{
s_board = resolve_board();
if (!s_board ||
s_board->codec.open(kBitsPerSample, kCodecChannels, sample_rate_hz) != 0)
s_board->openAudioSession(kAudioOwner,
kBitsPerSample,
kCodecChannels,
sample_rate_hz,
true) != 0)
{
s_board = nullptr;
return false;
}
s_board->codec.setVolume(s_board->getMessageToneVolume());
s_board->codec.setGain(kDefaultGainDb);
s_board->codec.setMute(false);
s_board->codec.setOutMute(false);
if (!s_board->audioSetVolume(kAudioOwner,
s_board->getMessageToneVolume()) ||
!s_board->audioSetGain(kAudioOwner, kDefaultGainDb) ||
!s_board->audioSetMute(kAudioOwner, false) ||
!s_board->audioSetOutMute(kAudioOwner, false))
{
s_board->closeAudioSession(kAudioOwner);
s_board = nullptr;
return false;
}
return true;
}
@@ -66,9 +77,7 @@ void close_audio()
{
if (s_board)
{
s_board->codec.setMute(true);
s_board->codec.setOutMute(true);
s_board->codec.close();
s_board->closeAudioSession(kAudioOwner);
}
s_board = nullptr;
}
@@ -77,18 +86,12 @@ bool read_mono(int16_t* pcm, std::size_t sample_count)
{
if (!s_board || !pcm || sample_count == 0 ||
sample_count > kMaxCodec2SamplesPerFrame ||
s_board->codec.read(reinterpret_cast<uint8_t*>(s_codec_pcm),
sample_count * kCodecChannels * sizeof(int16_t)) != 0)
s_board->audioRead(kAudioOwner,
reinterpret_cast<uint8_t*>(pcm),
sample_count * sizeof(int16_t)) != 0)
{
return false;
}
for (std::size_t index = 0; index < sample_count; ++index)
{
const int32_t left = s_codec_pcm[index * kCodecChannels];
const int32_t right = s_codec_pcm[(index * kCodecChannels) + 1U];
pcm[index] = static_cast<int16_t>((left + right) / 2);
}
return true;
}
@@ -100,14 +103,9 @@ bool write_mono(const int16_t* pcm, std::size_t sample_count)
return false;
}
for (std::size_t index = 0; index < sample_count; ++index)
{
s_codec_pcm[index * kCodecChannels] = pcm[index];
s_codec_pcm[(index * kCodecChannels) + 1U] = pcm[index];
}
return s_board->codec.write(reinterpret_cast<uint8_t*>(s_codec_pcm),
sample_count * kCodecChannels *
sizeof(int16_t)) == 0;
return s_board->audioWrite(kAudioOwner,
reinterpret_cast<const uint8_t*>(pcm),
sample_count * sizeof(int16_t)) == 0;
}
uint8_t speaker_volume()
@@ -126,7 +124,7 @@ void set_speaker_volume(uint8_t volume_percent)
}
if (s_board)
{
s_board->codec.setVolume(volume);
(void)s_board->audioSetVolume(kAudioOwner, volume);
}
}
@@ -183,6 +183,13 @@ uint32_t millis()
return sys::millis_now();
}
#endif
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
using SstvBoard = TLoRaPagerBoard;
#else
using SstvBoard = boards::tlora_pager::TLoRaPagerBoard;
#endif
constexpr uint32_t kTaskStack = 12288;
constexpr uint32_t kTaskDelayMs = 2;
constexpr uint32_t kCompleteHoldMs = 2000;
@@ -966,17 +973,71 @@ void render_line_from_buffer(uint8_t line_rgb[320][4], e_mode mode, uint16_t lin
s_has_image = true;
}
void sstv_task(void*)
bool open_sstv_audio(SstvBoard* board)
{
if (!board)
{
return false;
}
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
if (board->codec.open(kBitsPerSample, kChannels, kCodecSampleRate) != 0)
{
return false;
}
board->codec.setGain(kMicGainDb);
board->codec.setMute(false);
board->codec.setOutMute(true);
board->codec.setVolume(0);
board->powerControl(POWER_SPEAK, false);
return true;
#else
constexpr auto kOwner = boards::tlora_pager::PagerAudioOwner::Sstv;
if (board->openAudioSession(
kOwner, kBitsPerSample, kChannels, kCodecSampleRate, false) != 0)
{
return false;
}
if (!board->audioSetGain(kOwner, kMicGainDb) ||
!board->audioSetMute(kOwner, false) ||
!board->audioSetOutMute(kOwner, true) ||
!board->audioSetVolume(kOwner, 0))
{
board->closeAudioSession(kOwner);
return false;
}
return true;
#endif
}
int read_sstv_audio(SstvBoard* board, uint8_t* buffer, size_t size)
{
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
TLoRaPagerBoard* board = TLoRaPagerBoard::getInstance();
return board ? board->codec.read(buffer, size) : -1;
#else
boards::tlora_pager::TLoRaPagerBoard* board = boards::tlora_pager::TLoRaPagerBoard::getInstance();
return board ? board->audioRead(
boards::tlora_pager::PagerAudioOwner::Sstv,
buffer,
size)
: -1;
#endif
int prev_volume = -1;
bool prev_out_mute = false;
bool restore_amp = false;
bool prev_amp = true;
}
void close_sstv_audio(SstvBoard* board)
{
if (!board)
{
return;
}
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
board->codec.close();
#else
board->closeAudioSession(boards::tlora_pager::PagerAudioOwner::Sstv);
#endif
}
void sstv_task(void*)
{
SstvBoard* board = SstvBoard::getInstance();
if (!board)
{
set_error("Board not ready");
@@ -986,7 +1047,7 @@ void sstv_task(void*)
return;
}
if (board->codec.open(kBitsPerSample, kChannels, kCodecSampleRate) != 0)
if (!open_sstv_audio(board))
{
set_error("Codec open failed");
set_status(sstv::State::Error, 0, 0.0f, 0.0f, false);
@@ -996,18 +1057,6 @@ void sstv_task(void*)
}
s_codec_open = true;
board->codec.setGain(kMicGainDb);
board->codec.setMute(false);
prev_volume = board->codec.getVolume();
prev_out_mute = board->codec.getOutMute();
board->codec.setOutMute(true);
board->codec.setVolume(0);
#ifdef USING_XL9555_EXPANDS
prev_amp = board->io.digitalRead(EXPANDS_AMP_EN);
restore_amp = true;
#endif
// Disable speaker amplifier during RX to avoid feedback/noise.
board->powerControl(POWER_SPEAK, false);
SSTV_LOG("[SSTV] codec open ok (rate=%lu, decode=%lu, bits=%u, ch=%u)\n",
static_cast<unsigned long>(kCodecSampleRate),
static_cast<unsigned long>(kSampleRate),
@@ -1023,7 +1072,7 @@ void sstv_task(void*)
set_error("No audio buffer");
set_status(sstv::State::Error, 0, 0.0f, 0.0f, false);
s_codec_open = false;
board->codec.close();
close_sstv_audio(board);
s_active = false;
vTaskDelete(nullptr);
return;
@@ -1040,7 +1089,7 @@ void sstv_task(void*)
set_error("No decode buffers");
set_status(sstv::State::Error, 0, 0.0f, 0.0f, false);
s_codec_open = false;
board->codec.close();
close_sstv_audio(board);
s_active = false;
vTaskDelete(nullptr);
return;
@@ -1063,7 +1112,7 @@ void sstv_task(void*)
set_error("No framebuffer");
set_status(sstv::State::Error, 0, 0.0f, 0.0f, false);
s_codec_open = false;
board->codec.close();
close_sstv_audio(board);
s_active = false;
vTaskDelete(nullptr);
return;
@@ -1123,8 +1172,9 @@ void sstv_task(void*)
while (!s_stop)
{
int read_state = board->codec.read(reinterpret_cast<uint8_t*>(buffer),
samples_per_read * sizeof(int16_t));
int read_state = read_sstv_audio(board,
reinterpret_cast<uint8_t*>(buffer),
samples_per_read * sizeof(int16_t));
if (read_state != 0)
{
set_error("Audio read failed");
@@ -1489,16 +1539,7 @@ void sstv_task(void*)
if (s_codec_open)
{
if (prev_volume >= 0)
{
board->codec.setVolume(static_cast<uint8_t>(prev_volume));
}
board->codec.setOutMute(prev_out_mute);
if (restore_amp)
{
board->powerControl(POWER_SPEAK, prev_amp);
}
board->codec.close();
close_sstv_audio(board);
s_codec_open = false;
}
@@ -163,9 +163,16 @@ int codecOpen(Session* session, uint8_t bits_per_sample, uint8_t channels, uint3
{
return -1;
}
int state = board->codec.open(bits_per_sample, channels, sample_rate);
constexpr auto kOwner = ::boards::tlora_pager::PagerAudioOwner::Walkie;
int state = board->openAudioSession(
kOwner, bits_per_sample, channels, sample_rate, true);
if (state == 0)
{
if (!board->audioSetOutMute(kOwner, false))
{
board->closeAudioSession(kOwner);
return -1;
}
session->codec_open = true;
}
return state;
@@ -178,20 +185,28 @@ void codecClose(Session* session)
{
return;
}
board->codec.close();
board->closeAudioSession(::boards::tlora_pager::PagerAudioOwner::Walkie);
session->codec_open = false;
}
int codecRead(Session* session, uint8_t* buffer, size_t size)
{
::boards::tlora_pager::TLoRaPagerBoard* board = resolveBoard(session);
return board ? board->codec.read(buffer, size) : -1;
return board ? board->audioRead(
::boards::tlora_pager::PagerAudioOwner::Walkie,
buffer,
size)
: -1;
}
int codecWrite(Session* session, uint8_t* buffer, size_t size)
{
::boards::tlora_pager::TLoRaPagerBoard* board = resolveBoard(session);
return board ? board->codec.write(buffer, size) : -1;
return board ? board->audioWrite(
::boards::tlora_pager::PagerAudioOwner::Walkie,
buffer,
size)
: -1;
}
void codecSetVolume(Session* session, uint8_t level)
@@ -199,7 +214,8 @@ void codecSetVolume(Session* session, uint8_t level)
::boards::tlora_pager::TLoRaPagerBoard* board = resolveBoard(session);
if (board)
{
board->codec.setVolume(level);
(void)board->audioSetVolume(
::boards::tlora_pager::PagerAudioOwner::Walkie, level);
}
}
@@ -208,7 +224,8 @@ void codecSetGain(Session* session, float db_value)
::boards::tlora_pager::TLoRaPagerBoard* board = resolveBoard(session);
if (board)
{
board->codec.setGain(db_value);
(void)board->audioSetGain(
::boards::tlora_pager::PagerAudioOwner::Walkie, db_value);
}
}
@@ -217,7 +234,9 @@ void codecSetMute(Session* session, bool enabled)
::boards::tlora_pager::TLoRaPagerBoard* board = resolveBoard(session);
if (board)
{
board->codec.setMute(enabled);
constexpr auto kOwner = ::boards::tlora_pager::PagerAudioOwner::Walkie;
(void)board->audioSetMute(kOwner, enabled);
(void)board->audioSetOutMute(kOwner, enabled);
}
}
@@ -33,3 +33,4 @@ Each entry must be classified before it drives code changes.
| RCNF-006 | Announce validation | The conformance smoke now verifies `microReticulum` announce vectors structurally and cryptographically, including signature validation and destination-hash validation. |
| RCNF-007 | Runtime state ownership | Trail Mate has explicit transport, link, resource, propagation, propagation-service, and delivery runtime helpers for the current embedded Reticulum runtime ownership contract. |
| RCNF-008 | Supported-subset golden baseline | Trail Mate has deterministic product-subset golden vectors for the Reticulum/LXMF bytes currently used by supported business paths; canonical Python Reticulum/LXMF cross-check remains a later conformance upgrade. |
| RCNF-009 | Wi-Fi gateway final LinkRequest delivery | A Header 2 LinkRequest addressed to a local destination is now accepted as final delivery when it arrives from the configured Wi-Fi gateway, even when the retained transport id belongs to that gateway. Transport-id matching remains required for non-local forwarding and non-gateway ingress. |