mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-04-18 06:56:03 -04:00
Create Flex RX app
Can stream data but still trying to collect frame for actual message
This commit is contained in:
parent
288f6bd517
commit
23f3fffe53
@ -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);
|
||||
|
@ -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);
|
||||
|
6
firmware/application/external/external.cmake
vendored
6
firmware/application/external/external.cmake
vendored
@ -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
|
||||
)
|
||||
|
7
firmware/application/external/external.ld
vendored
7
firmware/application/external/external.ld
vendored
@ -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
|
||||
}
|
||||
|
70
firmware/application/external/flex/main.cpp
vendored
Normal file
70
firmware/application/external/flex/main.cpp
vendored
Normal 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,
|
||||
};
|
||||
}
|
261
firmware/application/external/flex/ui_flex_rx.cpp
vendored
Normal file
261
firmware/application/external/flex/ui_flex_rx.cpp
vendored
Normal 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
|
96
firmware/application/external/flex/ui_flex_rx.hpp
vendored
Normal file
96
firmware/application/external/flex/ui_flex_rx.hpp
vendored
Normal 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__*/
|
@ -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
|
||||
|
465
firmware/baseband/proc_flex.cpp
Normal file
465
firmware/baseband/proc_flex.cpp
Normal 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;
|
||||
}
|
177
firmware/baseband/proc_flex.hpp
Normal file
177
firmware/baseband/proc_flex.hpp
Normal 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__*/
|
@ -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};
|
||||
|
Loading…
x
Reference in New Issue
Block a user