Create Flex RX app

Can stream data but still trying to collect frame for actual message
This commit is contained in:
RocketGod 2025-04-10 14:16:56 -07:00
parent 288f6bd517
commit 23f3fffe53
11 changed files with 1152 additions and 0 deletions

View File

@ -280,6 +280,11 @@ void set_pocsag() {
send_message(&message);
}
void set_flex() {
const FlexConfigureMessage message{};
send_message(&message);
}
void set_adsb() {
const ADSBConfigureMessage message{};
send_message(&message);

View File

@ -74,6 +74,7 @@ void kill_afsk();
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
void set_fsk(const size_t deviation);
void set_aprs(const uint32_t baudrate);
void set_flex();
void set_btlerx(uint8_t channel_number);
void set_btletx(uint8_t channel_number, char* macAddress, char* advertisementData, uint8_t pduType);

View File

@ -207,6 +207,11 @@ set(EXTCPPSRC
#gfxEQ
external/gfxeq/main.cpp
external/gfxeq/ui_gfxeq.cpp
#flex_RX
external/flex/main.cpp
external/flex/ui_flex_rx.cpp
)
set(EXTAPPLIST
@ -260,4 +265,5 @@ set(EXTAPPLIST
scanner
level
gfxeq
flex_rx
)

View File

@ -73,6 +73,7 @@ MEMORY
ram_external_app_scanner (rwx) : org = 0xADE00000, len = 32k
ram_external_app_level (rwx) : org = 0xADE10000, len = 32k
ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k
ram_external_app_flex_rx (rwx) : org = 0xADE30000, len = 32k
}
SECTIONS
@ -375,4 +376,10 @@ SECTIONS
KEEP(*(.external_app.app_gfxeq.application_information));
*(*ui*external_app*gfxeq*);
} > ram_external_app_gfxeq
.external_app_flex_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_flex_rx.application_information));
*(*ui*external_app*flex_rx*);
} > ram_external_app_flex_rx
}

View File

@ -0,0 +1,70 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui.hpp"
#include "ui_flex_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::flex_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<FlexRxView>();
}
} // namespace ui::external_app::flex_rx
extern "C" {
__attribute__((section(".external_app.app_flex_rx.application_information"), used)) application_information_t _application_information_flex_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::flex_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Flex RX",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xF8,
0x1F,
0x04,
0x20,
0x02,
0x40,
0xFF,
0xFF,
0xFF,
0xFF,
0xAB,
0xDF,
0xAB,
0xDF,
0xFF,
0xFF,
0xFF,
0xFF,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = */ {'P', 'F', 'L', 'X'},
/*.m4_app_offset = */ 0x00000000,
};
}

View File

@ -0,0 +1,261 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui_flex_rx.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "portapack.hpp"
#include "portapack_shared_memory.hpp"
namespace ui::external_app::flex_rx {
void FlexLogger::log_decoded(const Timestamp& timestamp, const std::string& data) {
log_file.write_entry(timestamp, data);
}
void FlexRxView::focus() {
field_frequency.focus();
}
FlexRxView::FlexRxView(NavigationView& nav)
: nav_{nav} {
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&rssi, &channel, &field_rf_amp, &field_lna, &field_vga,
&field_volume, &field_frequency, &check_log, &text_debug,
&console, &sync_status});
field_frequency.set_value(929614000);
field_frequency.set_step(100);
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
};
logger = std::make_unique<FlexLogger>();
if (logger)
logger->append(logs_dir / u"FLEX.TXT");
baseband::set_flex();
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
portapack::receiver_model.enable();
}
FlexRxView::~FlexRxView() {
audio::output::stop();
portapack::receiver_model.disable();
baseband::shutdown();
}
uint8_t diff_bit_count(uint32_t left, uint32_t right) {
uint32_t diff = left ^ right;
uint8_t count = 0;
for (size_t i = 0; i < sizeof(diff) * 8; ++i) {
if (((diff >> i) & 0x1) == 1)
++count;
}
return count;
}
void FlexRxView::on_packet(const FlexPacketMessage& message) {
const auto& packet = message.packet;
const auto& batch = packet.batch();
auto timestamp = to_string_datetime(packet.timestamp(), HM);
auto bitrate = packet.bitrate();
std::string str_log = timestamp + " " + to_string_dec_uint(bitrate) + " ";
if (batch.size() < 8) return; // Ensure full block
uint32_t frame_info = batch[0];
size_t vsa = (frame_info >> 10) & 0x3F;
size_t asa = ((frame_info >> 8) & 0x03) + 1;
for (size_t j = asa; j < vsa && j < batch.size(); j++) {
uint32_t address_word = batch[j];
uint32_t data = address_word >> 11;
uint32_t parity = address_word & 0x7FF;
uint32_t syndrome = 0;
uint32_t temp = data;
for (int k = 0; k < 21; ++k) {
if (temp & 1) syndrome ^= (0x7FF >> k);
temp >>= 1;
}
syndrome ^= parity;
if (syndrome != 0) {
uint32_t error_pos = 0;
bool correctable = false;
for (int k = 0; k < 10; ++k) {
if (syndrome == (static_cast<uint32_t>(1) << k)) {
error_pos = k;
correctable = true;
break;
}
}
if (correctable && error_pos < 21) {
data ^= (1 << error_pos);
} else {
continue;
}
}
bool long_address = (data & 0x7FFFF) < 0x008001 || ((data & 0x7FFFF) > 0x1E0000 && (data & 0x7FFFF) < 0x1F0001) || (data & 0x7FFFF) > 0x1F7FFE;
size_t vb = vsa + j - asa;
if (vb >= batch.size()) continue;
uint32_t vector_word = batch[vb];
uint32_t vector_data = vector_word >> 11;
uint32_t vector_parity = vector_word & 0x7FF;
syndrome = 0;
temp = vector_data;
for (int k = 0; k < 21; ++k) {
if (temp & 1) syndrome ^= (0x7FF >> k);
temp >>= 1;
}
syndrome ^= vector_parity;
if (syndrome != 0) {
uint32_t error_pos = 0;
bool correctable = false;
for (int k = 0; k < 10; ++k) {
if (syndrome == (static_cast<uint32_t>(1) << k)) {
error_pos = k;
correctable = true;
break;
}
}
if (correctable && error_pos < 21) {
vector_data ^= (1 << error_pos);
} else {
continue;
}
}
uint8_t vector_type = (vector_data >> 4) & 0x07;
if (long_address && j + 1 >= batch.size()) continue;
std::string display_text;
uint32_t address;
if (!long_address) {
address = (data & 0x7FFFF) - 32768;
display_text = to_string_dec_uint(address);
} else {
uint32_t second_word = batch[j + 1];
uint32_t second_data = second_word >> 11;
syndrome = 0;
temp = second_data;
for (int k = 0; k < 21; ++k) {
if (temp & 1) syndrome ^= (0x7FF >> k);
temp >>= 1;
}
syndrome ^= (second_word & 0x7FF);
if (syndrome == 0) {
address = ((second_data ^ 0x1FFFFF) << 15) + 2068480 + (data & 0x7FFFF);
display_text = to_string_dec_uint(address);
} else {
continue;
}
j++;
}
console.writeln(display_text);
if (logger && logging) logger->log_decoded(packet.timestamp(), str_log + display_text);
if (vector_type == 5) { // Alpha message
int w1 = (vector_data >> 7) & 0x7F;
int w2 = ((vector_data >> 14) & 0x7F) + w1 - 1;
if (w1 < 0 || w2 < 0 || static_cast<size_t>(w1) >= batch.size() || static_cast<size_t>(w2) >= batch.size()) continue;
std::string message_text;
for (int k = w1; k <= w2; k++) {
uint32_t msg_word = batch[k];
data = msg_word >> 11;
parity = msg_word & 0x7FF;
syndrome = 0;
temp = data;
for (int m = 0; m < 21; ++m) {
if (temp & 1) syndrome ^= (0x7FF >> m);
temp >>= 1;
}
syndrome ^= parity;
if (syndrome != 0) {
uint32_t error_pos = 0;
bool correctable = false;
for (int m = 0; m < 10; ++m) {
if (syndrome == (static_cast<uint32_t>(1) << m)) {
error_pos = m;
correctable = true;
break;
}
}
if (correctable && error_pos < 21) {
data ^= (1 << error_pos);
} else {
continue;
}
}
for (int m = 0; m < 20; m += 7) {
uint8_t byte = (data >> (13 - m)) & 0x7F;
if (byte >= 32 && byte < 127) message_text += (char)byte;
}
}
if (!message_text.empty()) {
console.writeln(message_text);
if (logger && logging) logger->log_decoded(packet.timestamp(), str_log + message_text);
}
}
}
console_color++;
}
void FlexRxView::on_stats(const FlexStatsMessage& stats) {
if (stats.has_sync != last_has_sync) {
std::string str_console = "\x1B\x31";
if (stats.has_sync) {
str_console += "SYNC ACQUIRED - Baud: " + to_string_dec_uint(stats.baud_rate);
} else if (last_has_sync) {
str_console += "SYNC LOST";
}
console.writeln(str_console);
sync_status.set_color(stats.has_sync ? Color::green() : Color::red());
}
if (stats.baud_rate != last_baud_rate ||
stats.has_sync != last_has_sync ||
stats.current_frames != last_frames ||
stats.current_bits != last_bits) {
std::string debug_text = "BR:" + to_string_dec_uint(stats.baud_rate) +
" Sync:" + (stats.has_sync ? "Y" : "N") +
" Fr:" + to_string_dec_uint(stats.current_frames);
text_debug.set(debug_text);
last_baud_rate = stats.baud_rate;
last_has_sync = stats.has_sync;
last_frames = stats.current_frames;
last_bits = stats.current_bits;
}
}
void FlexRxView::on_freqchg(int64_t freq) {
field_frequency.set_value(freq);
}
} // namespace ui::external_app::flex_rx

View File

@ -0,0 +1,96 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_FLEX_RX_H__
#define __UI_FLEX_RX_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
namespace ui::external_app::flex_rx {
class FlexLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_decoded(const Timestamp& timestamp, const std::string& data);
private:
LogFile log_file{};
};
class FlexRxView : public View {
public:
FlexRxView(NavigationView& nav);
~FlexRxView();
void focus() override;
std::string title() const override { return "Flex RX"; };
private:
void on_packet(const FlexPacketMessage& message);
void on_stats(const FlexStatsMessage& stats);
void on_freqchg(int64_t freq);
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{"rx_flex", app_settings::Mode::RX};
uint8_t console_color{0};
bool logging{false};
std::string current_message{};
uint32_t last_address{0};
uint16_t last_baud_rate{0};
bool last_has_sync{false};
uint8_t last_frames{0};
uint32_t last_bits{0};
RFAmpField field_rf_amp{{13 * 8, 0 * 16}};
LNAGainField field_lna{{15 * 8, 0 * 16}};
VGAGainField field_vga{{18 * 8, 0 * 16}};
RSSI rssi{{21 * 8, 0, 6 * 8, 4}};
Channel channel{{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{{28 * 8, 0 * 16}};
RxFrequencyField field_frequency{{0 * 8, 0 * 16}, nav_};
Checkbox check_log{{0 * 8, 1 * 16}, 3, LanguageHelper::currentMessages[LANG_LOG], false};
Text text_debug{{0 * 8, 12 + 2 * 16, screen_width, 16}, ""};
Console console{{0, 4 * 16, 240, 176}};
Rectangle sync_status{{214, 1 * 16, 12, 12}, Color::red()};
std::unique_ptr<FlexLogger> logger{};
MessageHandlerRegistration message_handler_packet{
Message::ID::FlexPacket,
[this](Message* const p) {
const auto message = static_cast<const FlexPacketMessage*>(p);
this->on_packet(*message);
}};
MessageHandlerRegistration message_handler_stats{
Message::ID::FlexStats,
[this](Message* const p) {
const auto message = static_cast<const FlexStatsMessage*>(p);
this->on_stats(*message);
}};
MessageHandlerRegistration message_handler_freqchg{
Message::ID::FreqChangeCommand,
[this](Message* const p) {
const auto message = static_cast<const FreqChangeCommandMessage*>(p);
this->on_freqchg(message->freq);
}};
};
} // namespace ui::external_app::flex_rx
#endif /*__UI_FLEX_RX_H__*/

View File

@ -441,6 +441,13 @@ set(MODE_CPPSRC
)
DeclareTargets(PPO2 pocsag2)
### FLEX RX
set(MODE_CPPSRC
proc_flex.cpp
)
DeclareTargets(PFLX flex_rx)
### RDS
set(MODE_CPPSRC

View File

@ -0,0 +1,465 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "proc_flex.hpp"
#include "event_m4.hpp"
#include "audio_dma.hpp"
#include "dsp_fir_taps.hpp"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstddef>
#include <memory>
namespace {
uint8_t diff_bit_count(uint32_t left, uint32_t right) {
uint32_t diff = left ^ right;
uint8_t count = 0;
for (size_t i = 0; i < sizeof(diff) * 8; ++i) {
if (((diff >> i) & 0x1) == 1)
++count;
}
return count;
}
} // namespace
FlexProcessor::FlexProcessor()
: BasebandProcessor(),
state(State::Idle),
sync_buffer(0),
frame_buffer(),
current_word(0),
bit_count(0),
current_baud_rate(1600) {
}
void FlexAudioNormalizer::execute_in_place(const buffer_f32_t& audio) {
if (counter_ >= 24'000) {
max_ *= 0.9f;
min_ *= 0.9f;
counter_ = 0;
calculate_thresholds();
}
counter_ += audio.count;
for (size_t i = 0; i < audio.count; ++i) {
auto& val = audio.p[i];
if (val > max_) {
max_ = val;
calculate_thresholds();
}
if (val < min_) {
min_ = val;
calculate_thresholds();
}
if (val >= t_high_)
val = 1.0f;
else if (val >= t_mid_high_)
val = 0.33f;
else if (val <= t_low_)
val = -1.0f;
else if (val <= t_mid_low_)
val = -0.33f;
else
val = 0.0f;
}
}
void FlexAudioNormalizer::calculate_thresholds() {
auto center = (max_ + min_) / 2.0f;
auto range = (max_ - min_) / 2.0f;
auto step = range / 3.0f;
t_high_ = center + step * 1.5f;
t_mid_high_ = center + step * 0.5f;
t_mid_low_ = center - step * 0.5f;
t_low_ = center - step * 1.5f;
}
void FlexBitQueue::push(uint8_t symbol) {
data_ = (data_ << 2) | (symbol & 0x3);
if (count_ < max_size_) ++count_;
}
bool FlexBitQueue::pop(uint8_t& symbol) {
if (count_ == 0) return false;
--count_;
symbol = (data_ >> (count_ * 2)) & 0x3;
return true;
}
void FlexBitQueue::reset() {
data_ = 0;
count_ = 0;
}
uint8_t FlexBitQueue::size() const {
return count_;
}
uint32_t FlexBitQueue::data() const {
return data_;
}
void FlexBitExtractor::extract_bits(const buffer_f32_t& audio) {
for (size_t i = 0; i < audio.count; ++i) {
auto sample = audio.p[i];
if (current_rate_) {
if (current_rate_->handle_sample(sample)) {
uint8_t symbol;
if (sample >= 0.66f)
symbol = 0x0;
else if (sample >= 0.0f)
symbol = 0x1;
else if (sample >= -0.66f)
symbol = 0x2;
else
symbol = 0x3;
bits_.push(symbol);
}
} else {
for (auto& rate : known_rates_) {
if (rate.handle_sample(sample) &&
diff_bit_count(rate.bits.data(), sync1_pattern) <= 4) {
rate.is_stable = true;
current_rate_ = &rate;
}
}
}
}
}
void FlexBitExtractor::configure(uint32_t sample_rate) {
sample_rate_ = sample_rate;
for (auto& rate : known_rates_) {
rate.sample_interval = sample_rate / static_cast<float>(rate.baud_rate);
}
}
void FlexBitExtractor::reset() {
current_rate_ = nullptr;
for (auto& rate : known_rates_)
rate.reset();
}
uint16_t FlexBitExtractor::baud_rate() const {
return current_rate_ ? current_rate_->baud_rate : 0;
}
bool FlexBitExtractor::RateInfo::handle_sample(float sample) {
samples_until_next -= 1;
if (samples_until_next > 0) return false;
bool bit_pushed = false;
float delta = std::abs(sample - prev_value);
switch (state) {
case State::WaitForSample:
state = State::ReadyToSend;
break;
case State::ReadyToSend:
if (!is_stable && delta > 0.33f) {
samples_until_next += (sample_interval / 8.0f);
} else {
state = State::WaitForSample;
bit_pushed = true;
bits.push(sample >= 0.0f ? (sample >= 0.66f ? 0x0 : 0x1) : (sample >= -0.66f ? 0x2 : 0x3));
}
break;
}
samples_until_next += sample_interval;
prev_value = sample;
return bit_pushed;
}
void FlexBitExtractor::RateInfo::reset() {
state = State::WaitForSample;
samples_until_next = 0.0f;
prev_value = 0.0f;
is_stable = false;
bits.reset();
}
void FlexCodewordExtractor::process_bits() {
uint8_t symbol;
while (bits_.pop(symbol)) {
data_ = (data_ << 2) | symbol;
bit_count_ += 2;
if (bit_count_ >= data_bit_count) {
if (!has_sync_) {
if (diff_bit_count(data_, sync1_codeword) <= 4) {
handle_sync();
}
} else {
save_current_codeword();
}
}
}
}
void FlexCodewordExtractor::flush() {
if (word_count_ == 0) return;
pad_idle();
handle_batch_complete();
}
void FlexCodewordExtractor::reset() {
clear_data_bits();
has_sync_ = false;
word_count_ = 0;
}
void FlexCodewordExtractor::clear_data_bits() {
data_ = 0;
bit_count_ = 0;
}
void FlexCodewordExtractor::take_one_symbol() {
uint8_t symbol;
if (bits_.pop(symbol)) {
data_ = (data_ << 2) | symbol;
if (bit_count_ < data_bit_count)
bit_count_ += 2;
}
}
void FlexCodewordExtractor::handle_sync() {
clear_data_bits();
has_sync_ = true;
word_count_ = 0;
}
void FlexCodewordExtractor::save_current_codeword() {
batch_[word_count_++] = data_;
clear_data_bits();
if (word_count_ >= flex_batch_size) {
handle_batch_complete();
}
}
void FlexCodewordExtractor::handle_batch_complete() {
on_batch_(*this);
has_sync_ = false;
word_count_ = 0;
}
void FlexCodewordExtractor::pad_idle() {
while (word_count_ < flex_batch_size) {
batch_[word_count_++] = 0xAAAAAAAA;
}
}
void FlexProcessor::execute(const buffer_c8_t& buffer) {
if (!configured) return;
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
auto audio = demod.execute(channel_out, audio_buffer);
squelch.set_threshold(0.01);
squelch_history = (squelch_history << 1) | (squelch.execute(audio) ? 1 : 0);
lpf.execute_in_place(audio);
normalizer.execute_in_place(audio);
audio_output.write(audio);
bit_extractor.extract_bits(audio);
word_extractor.process_bits();
static uint64_t sync_buffer64 = 0;
static uint32_t block_buffer[8] = {0};
static uint8_t word_count = 0;
static uint8_t bit_count = 0;
for (size_t i = 0; i < audio.count; ++i) {
float sample = audio.p[i];
sync_buffer64 = (sync_buffer64 << 2);
if (sample >= 0.66f)
sync_buffer64 |= 0x0;
else if (sample >= 0.0f)
sync_buffer64 |= 0x1;
else if (sample >= -0.66f)
sync_buffer64 |= 0x2;
else
sync_buffer64 |= 0x3;
switch (state) {
case State::Idle:
if (squelch.execute(audio)) {
uint32_t sync1 = (sync_buffer64 >> 32) & 0xFFFF;
uint32_t sync2 = sync_buffer64 & 0xFFFF;
if (diff_bit_count(sync1, 0xA6C6) <= 2 && diff_bit_count(sync2, 0xAAAA) <= 2) {
uint32_t sync_pre = (sync_buffer64 >> 48) & 0xFFFF;
uint32_t sync_post = (sync_buffer64 >> 16) & 0xFFFF;
if (diff_bit_count(sync_pre, 0x870C) <= 2 && diff_bit_count(sync_post, 0x78F3) <= 2) {
current_baud_rate = 1600;
state = State::FrameData;
word_count = 0;
bit_count = 0;
} else if (diff_bit_count(sync_pre, 0xB068) <= 2 && diff_bit_count(sync_post, 0x4F97) <= 2) {
current_baud_rate = 3200;
state = State::FrameData;
word_count = 0;
bit_count = 0;
} else if (diff_bit_count(sync_pre, 0xDEA0) <= 2 && diff_bit_count(sync_post, 0x215F) <= 2) {
current_baud_rate = 6400;
state = State::FrameData;
word_count = 0;
bit_count = 0;
}
}
}
break;
case State::FrameData:
block_buffer[word_count] = (block_buffer[word_count] << 2);
if (sample >= 0.66f)
block_buffer[word_count] |= 0x0;
else if (sample >= 0.0f)
block_buffer[word_count] |= 0x1;
else if (sample >= -0.66f)
block_buffer[word_count] |= 0x2;
else
block_buffer[word_count] |= 0x3;
bit_count += 2;
if (bit_count >= 32) {
word_count++;
bit_count = 0;
if (word_count >= 8) {
packet.set_flag(FlexPacketFlag::FLEX_NORMAL);
packet.set_timestamp(Timestamp::now());
packet.set_bitrate(current_baud_rate);
flex_batch_t batch{};
for (size_t j = 0; j < 8; j++) {
batch[j] = block_buffer[j];
}
packet.set(batch);
FlexPacketMessage message(packet);
shared_memory.application_queue.push(message);
word_count = 0;
state = State::Idle; // Reset to find next sync
}
}
break;
default:
state = State::Idle;
break;
}
}
samples_processed += buffer.count;
if (samples_processed >= stat_update_threshold) {
send_stats();
samples_processed -= stat_update_threshold;
}
}
void FlexProcessor::process_frame() {
// No longer used; packet sent directly in execute()
}
void FlexProcessor::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::FlexConfigure:
configure();
break;
case Message::ID::NBFMConfigure:
squelch.set_threshold(0.01);
break;
case Message::ID::AudioBeep:
on_beep_message(*reinterpret_cast<const AudioBeepMessage*>(message));
break;
default:
break;
}
}
void FlexProcessor::configure() {
constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor;
constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor;
constexpr size_t channel_filter_output_fs = decim_1_output_fs / 2;
constexpr size_t demod_input_fs = channel_filter_output_fs;
decim_0.configure(taps_200k_decim_0.taps);
decim_1.configure(taps_16k0_decim_1.taps);
channel_filter.configure(taps_16k0_channel.taps, 2);
demod.configure(demod_input_fs, 4800);
audio_output.configure(false);
bit_extractor.configure(demod_input_fs);
configured = true;
}
void FlexProcessor::flush() {
word_extractor.flush();
}
void FlexProcessor::reset() {
state = State::Idle;
sync_buffer = 0;
frame_buffer.clear();
current_word = 0;
bit_count = 0;
bits.reset();
bit_extractor.reset();
word_extractor.reset();
samples_processed = 0;
}
void FlexProcessor::send_stats() const {
FlexStatsMessage message(
current_word,
frame_buffer.size(),
state != State::Idle,
current_baud_rate);
shared_memory.application_queue.push(message);
}
void FlexProcessor::send_packet() {
packet.set_flag(FlexPacketFlag::FLEX_NORMAL);
packet.set_timestamp(Timestamp::now());
packet.set_bitrate(bit_extractor.baud_rate());
packet.set(word_extractor.batch());
FlexPacketMessage message(packet);
shared_memory.application_queue.push(message);
}
void FlexProcessor::on_beep_message(const AudioBeepMessage& message) {
audio::dma::beep_start(message.freq, message.sample_rate, message.duration_ms);
}
int main() {
audio::dma::init_audio_out();
EventDispatcher event_dispatcher{std::make_unique<FlexProcessor>()};
event_dispatcher.run();
return 0;
}

View File

@ -0,0 +1,177 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __PROC_FLEX_H__
#define __PROC_FLEX_H__
#include "audio_output.hpp"
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "dsp_decimate.hpp"
#include "dsp_demodulate.hpp"
#include "dsp_iir_config.hpp"
#include "dsp_fir_taps.hpp"
#include "message.hpp"
#include "portapack_shared_memory.hpp"
#include "rssi_thread.hpp"
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
using flex_batch_t = std::array<uint32_t, flex_batch_size>;
class FlexAudioNormalizer {
public:
void execute_in_place(const buffer_f32_t& audio);
private:
void calculate_thresholds();
uint32_t counter_ = 0;
float min_ = 99.0f;
float max_ = -99.0f;
float t_high_ = 1.0f;
float t_mid_high_ = 0.33f;
float t_mid_low_ = -0.33f;
float t_low_ = -1.0f;
};
class FlexBitQueue {
public:
void push(uint8_t symbol);
bool pop(uint8_t& symbol);
void reset();
uint8_t size() const;
uint32_t data() const;
private:
uint32_t data_ = 0;
uint8_t count_ = 0;
static constexpr uint8_t max_size_ = sizeof(data_) * 8 / 2;
};
class FlexBitExtractor {
public:
FlexBitExtractor(FlexBitQueue& bits) : bits_{bits} {}
void extract_bits(const buffer_f32_t& audio);
void configure(uint32_t sample_rate);
void reset();
uint16_t baud_rate() const;
private:
static constexpr uint32_t sync1_pattern = 0xA6C6AAAA;
struct RateInfo {
enum class State : uint8_t {
WaitForSample,
ReadyToSend
};
const uint16_t baud_rate = 0;
float sample_interval = 0.0;
State state = State::WaitForSample;
float samples_until_next = 0.0;
float prev_value = 0.0f;
bool is_stable = false;
FlexBitQueue bits{};
bool handle_sample(float sample);
void reset();
};
std::array<RateInfo, 3> known_rates_{RateInfo{1600}, RateInfo{3200}, RateInfo{6400}};
FlexBitQueue& bits_;
uint32_t sample_rate_ = 0;
RateInfo* current_rate_ = nullptr;
};
class FlexCodewordExtractor {
public:
using batch_t = flex_batch_t;
using batch_handler_t = std::function<void(FlexCodewordExtractor&)>;
FlexCodewordExtractor(FlexBitQueue& bits, batch_handler_t on_batch) : bits_{bits}, on_batch_{on_batch} {}
void process_bits();
void flush();
void reset();
const batch_t& batch() const { return batch_; }
uint32_t current() const { return data_; }
uint8_t count() const { return word_count_; }
bool has_sync() const { return has_sync_; }
private:
static constexpr uint32_t sync1_codeword = 0xA6C6AAAA;
static constexpr uint8_t data_bit_count = sizeof(uint32_t) * 8;
void clear_data_bits();
void take_one_symbol();
void handle_sync();
void save_current_codeword();
void handle_batch_complete();
void pad_idle();
FlexBitQueue& bits_;
batch_handler_t on_batch_{};
bool has_sync_ = false;
uint32_t data_ = 0;
uint8_t bit_count_ = 0;
uint8_t word_count_ = 0;
batch_t batch_{};
};
class FlexProcessor : public BasebandProcessor {
public:
FlexProcessor();
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override;
private:
enum class State {
Idle,
FrameSync,
FrameData
};
static constexpr size_t baseband_fs = 3072000;
static constexpr uint8_t stat_update_interval = 10;
static constexpr uint32_t stat_update_threshold = baseband_fs / stat_update_interval;
void configure();
void flush();
void reset();
void send_stats() const;
void send_packet();
void process_frame();
void on_beep_message(const AudioBeepMessage& message);
bool configured = false;
std::array<complex16_t, 256> dst{};
const buffer_c16_t dst_buffer{dst.data(), dst.size()};
std::array<float, 16> audio{};
const buffer_f32_t audio_buffer{audio.data(), audio.size()};
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
dsp::decimate::FIRAndDecimateComplex channel_filter{};
dsp::demodulate::FM demod{};
FMSquelch squelch{};
uint64_t squelch_history = 0;
IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f}, {1.00000000f, -1.34896775f, 0.51398189f}}};
FlexAudioNormalizer normalizer{};
AudioOutput audio_output{};
FlexPacket packet{};
uint32_t samples_processed = 0;
FlexBitQueue bits{};
FlexBitExtractor bit_extractor{bits};
FlexCodewordExtractor word_extractor{bits, [this](FlexCodewordExtractor&) { send_packet(); }};
State state = State::Idle;
uint32_t sync_buffer = 0;
std::vector<uint32_t> frame_buffer;
uint32_t current_word = 0;
uint8_t bit_count = 0;
uint16_t current_baud_rate = 1600;
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
RSSIThread rssi_thread{};
};
#endif /*__PROC_FLEX_H__*/

View File

@ -130,6 +130,9 @@ class Message {
WeFaxRxConfigure = 73,
WeFaxRxStatusData = 74,
WeFaxRxImageData = 75,
FlexConfigure = 76,
FlexPacket = 77,
FlexStats = 78,
MAX
};
@ -142,6 +145,60 @@ class Message {
const ID id;
};
// FlexPacketFlag and FlexPacket definitions
enum FlexPacketFlag : uint8_t {
FLEX_NORMAL = 0,
CRC_ERROR = 1,
SYNC_ERROR = 2
};
constexpr size_t flex_batch_size = 88;
using flex_batch_t = std::array<uint32_t, flex_batch_size>;
class FlexPacket {
public:
void set(const flex_batch_t& batch) { batch_ = batch; }
void set_flag(FlexPacketFlag flag) { flag_ = flag; }
void set_timestamp(Timestamp timestamp) { timestamp_ = timestamp; }
void set_bitrate(uint16_t bitrate) { bitrate_ = bitrate; }
const flex_batch_t& batch() const { return batch_; }
FlexPacketFlag flag() const { return flag_; }
Timestamp timestamp() const { return timestamp_; }
uint16_t bitrate() const { return bitrate_; }
uint32_t operator[](size_t index) const { return batch_[index]; }
private:
flex_batch_t batch_{};
FlexPacketFlag flag_ = FLEX_NORMAL;
Timestamp timestamp_{};
uint16_t bitrate_ = 0;
};
class FlexConfigureMessage : public Message {
public:
constexpr FlexConfigureMessage()
: Message{ID::FlexConfigure} {}
// Add configuration fields if needed in the future
};
class FlexPacketMessage : public Message {
public:
constexpr FlexPacketMessage(const FlexPacket& p)
: Message{ID::FlexPacket}, packet{p} {}
const FlexPacket& packet;
};
class FlexStatsMessage : public Message {
public:
constexpr FlexStatsMessage(uint32_t cb, uint8_t cf, bool hs, uint16_t br)
: Message{ID::FlexStats}, current_bits{cb}, current_frames{cf}, has_sync{hs}, baud_rate{br} {}
uint32_t current_bits;
uint8_t current_frames;
bool has_sync;
uint16_t baud_rate;
};
struct RSSIStatistics {
uint32_t accumulator{0};
uint8_t min{0};