Rtty tx and rx (#2977)

This commit is contained in:
Totoo
2026-02-16 14:34:55 +01:00
committed by GitHub
parent 95eafeb812
commit 794daf5257
21 changed files with 1809 additions and 1 deletions
+104
View File
@@ -0,0 +1,104 @@
#include "baudot.hpp"
#include <cctype>
namespace ui::external_app::rtty_tx {
constexpr uint8_t CODE_FIGS = 0x1B;
constexpr uint8_t CODE_LTRS = 0x1F;
constexpr uint8_t CODE_SPACE = 0x04;
char BaudotCoder::get_char_mapping(bool is_figures, uint8_t index) const {
if (index >= 32) return 0; // Safety check
if (is_figures) {
// ITA2 FIGURES Table
const char figures[32] = {
0, '3', '\n', '-', ' ', '\'', '8', '7',
'\r', '$', '4', '\a', ',', '!', ':', '(',
'5', '+', ')', '2', '#', '6', '0', '1',
'9', '?', '&', 0, '.', '/', '=', 0};
return figures[index];
} else {
// ITA2 LETTERS Table
const char letters[32] = {
0, 'E', '\n', 'A', ' ', 'S', 'I', 'U',
'\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K',
'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q',
'O', 'B', 'G', 0, 'M', 'X', 'V', 0};
return letters[index];
}
}
// --- DECODE ---
char BaudotCoder::decode(uint8_t baudotCode) {
uint8_t code = baudotCode & 0x1F;
if (code == CODE_FIGS) {
shiftState = FIGURES;
return 0;
}
if (code == CODE_LTRS) {
shiftState = LETTERS;
return 0;
}
if (usos_enabled && shiftState == FIGURES && code == CODE_SPACE) {
shiftState = LETTERS;
return ' ';
}
return get_char_mapping((shiftState == FIGURES), code);
}
void BaudotCoder::encode(const std::string& src, uint8_t* dest, uint16_t* dest_length, uint16_t dest_max_size) {
uint16_t idx = 0;
for (char c : src) {
if (idx >= dest_max_size) break;
char upper_c = std::toupper(c);
uint8_t code = 0;
bool found = false;
bool target_is_figures = false;
if (upper_c == ' ') {
code = CODE_SPACE;
found = true;
} else {
for (int i = 1; i < 32; i++) {
if (get_char_mapping(false, i) == upper_c) {
code = i;
found = true;
target_is_figures = false;
break;
}
}
if (!found) {
for (int i = 1; i < 32; i++) {
if (get_char_mapping(true, i) == upper_c) {
code = i;
found = true;
target_is_figures = true;
break;
}
}
}
}
if (found) {
if (target_is_figures && shiftState != FIGURES) {
if (idx + 1 >= dest_max_size) break;
dest[idx++] = CODE_FIGS;
shiftState = FIGURES;
} else if (!target_is_figures && shiftState != LETTERS && code != CODE_SPACE) {
if (idx + 1 >= dest_max_size) break;
dest[idx++] = CODE_LTRS;
shiftState = LETTERS;
}
dest[idx++] = code;
}
}
if (dest_length) {
*dest_length = idx;
}
}
} // namespace ui::external_app::rtty_tx
+28
View File
@@ -0,0 +1,28 @@
#ifndef BAUDOT_HPP
#define BAUDOT_HPP
#include <cstdint>
#include <string>
namespace ui::external_app::rtty_tx {
class BaudotCoder {
public:
enum ShiftState { LETTERS,
FIGURES };
BaudotCoder()
: shiftState(LETTERS) {}
char decode(uint8_t baudotCode);
void encode(const std::string& src, uint8_t* dest, uint16_t* dest_length, uint16_t dest_max_size);
void set_usos(bool enable) { usos_enabled = enable; } // shift == set to letters too
private:
ShiftState shiftState;
bool usos_enabled = true;
char get_char_mapping(bool is_figures, uint8_t index) const;
};
} // namespace ui::external_app::rtty_tx
#endif // BAUDOT_HPP
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2026 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_rtty_tx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::rtty_tx {
void initialize_app(ui::NavigationView& nav) {
nav.push<RttyTxView>();
}
} // namespace ui::external_app::rtty_tx
extern "C" {
__attribute__((section(".external_app.app_rtty_tx.application_information"), used)) application_information_t _application_information_rtty_tx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::rtty_tx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "RTTY",
/*.bitmap_data = */ {
0x00,
0x00,
0xFE,
0x1F,
0xFE,
0x3F,
0x0E,
0x78,
0x0E,
0x70,
0x0E,
0x70,
0x0E,
0x70,
0x0E,
0x78,
0xFE,
0x3F,
0xFE,
0x1F,
0x8E,
0x07,
0x0E,
0x0F,
0x0E,
0x1E,
0x0E,
0x3C,
0x0E,
0x78,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_rttytx */ {'P', 'R', 'T', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+154
View File
@@ -0,0 +1,154 @@
/*
* Copyright (C) 2026 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_rtty_tx.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace ui;
namespace ui::external_app::rtty_tx {
void RttyTxView::focus() {
options_baud.focus();
}
RttyTxView::RttyTxView(NavigationView& nav)
: nav_{nav} {
add_children({&tx_view,
&labels,
&options_baud,
&options_shift,
&btn_message,
&text_message,
&check_inverted,
&options_tone,
&text_tones});
options_baud.on_change = [this](size_t, int32_t value) {
baud_conf = value;
};
options_baud.set_by_value(baud_conf);
options_shift.on_change = [this](size_t, int32_t value) {
shift = value;
refresh_tones();
};
options_shift.set_by_value(shift);
btn_message.on_select = [this](Button&) {
text_prompt(nav_, message, 64, ENTER_KEYBOARD_MODE_ALPHA, [this](std::string& new_message) {
message = new_message;
text_message.set(message); // maybe replace to console
});
};
text_message.set(message);
options_tone.set_by_value(mark_tone);
options_tone.on_change = [this](size_t, int32_t value) {
mark_tone = value;
refresh_tones();
};
check_inverted.on_select = [this](Checkbox&, bool) {
refresh_tones();
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_target_frequency(f);
};
};
tx_view.on_start = [this]() {
if (message.empty()) {
nav_.display_modal("Error", "Message is empty. Please set a message to transmit.");
return;
}
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
transmitter_model.set_baseband_bandwidth(2048000);
baseband::set_sample_rate(2048000, OversampleRate::None);
transmitter_model.set_sampling_rate(2048000);
transmitter_model.set_baseband_bandwidth(2048000);
transmitter_model.enable();
rtty_message.baud = baud_conf;
rtty_message.shift = shift;
rtty_message.inverted = false; // check_inverted.value(); //i already invert it with the mark and space calculation
rtty_message.mark_tone = mark;
rtty_message.space_tone = space;
rtty_message.stopbits = stop_bits;
baudot_encoder.set_usos(false); // to be compatible with all
baudot_encoder.encode(message, rtty_message.data, &rtty_message.data_len, rtty_message.max_len);
baseband::set_rtty_config(rtty_message);
tx_view.set_transmitting(true);
};
tx_view.on_stop = [this]() {
stop();
};
refresh_tones();
}
void RttyTxView::refresh_tones() {
std::string tones_str = "";
int16_t shift = options_shift.selected_index_value();
if (mark_tone == -32000) {
mark = -shift / 2;
space = shift / 2;
} else {
if (mark_tone < 0) {
space = -(shift + abs(mark_tone));
} else {
space = shift + mark_tone;
}
mark = mark_tone;
}
if (check_inverted.value()) {
int16_t tmp = mark;
mark = space;
space = tmp;
}
tones_str = "Mark:" + to_string_dec_int(mark) + " Hz, Space:" + to_string_dec_int(space) + " Hz";
text_tones.set(tones_str);
}
void RttyTxView::on_tx_progress(bool done) {
if (done) stop();
}
void RttyTxView::stop() {
tx_view.set_transmitting(false);
transmitter_model.disable();
baseband::shutdown();
}
RttyTxView::~RttyTxView() {
stop();
}
} // namespace ui::external_app::rtty_tx
+145
View File
@@ -0,0 +1,145 @@
/*
* Copyright (C) 2026 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_RTTY_TX_H__
#define __UI_RTTY_TX_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "ui_fileman.hpp"
#include "file_path.hpp"
#include "baudot.hpp"
using namespace ui;
namespace ui::external_app::rtty_tx {
class RttyTxView : public View {
public:
RttyTxView(NavigationView& nav);
~RttyTxView();
void focus() override;
std::string title() const override { return "RTTY"; };
private:
void on_tx_progress(bool done);
void stop();
void refresh_tones();
NavigationView& nav_;
RxRadioState radio_state_{};
uint32_t baud_conf = 4545; // default to 45.45 baud, which is common for RTTY.
uint32_t shift = 170; // 170 hz
std::string message = "PORTAPACK";
int32_t mark_tone = 0; // for option
int16_t mark = 0, space = 170;
uint8_t stop_bits = 3; // 2=1.0, 3=1.5, 4=2.0
app_settings::SettingsManager settings_{
"tx_rtty",
app_settings::Mode::TX,
{
{"baud_conf"sv, &baud_conf},
{"shift"sv, &shift},
{"message"sv, &message},
{"mark_tone"sv, &mark_tone},
}};
Labels labels{
{{UI_POS_X(0), UI_POS_Y(0)}, "Baud:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(14), UI_POS_Y(0)}, "Shift:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(15), UI_POS_Y(1)}, "Mark t:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(3)}, "Message:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_baud{
{UI_POS_X(7), UI_POS_Y(0)},
5,
{{"45", 4500},
{"45.45", 4545},
{"50", 5000},
{"75", 7500},
{"100", 10000},
{"110", 11000},
{"150", 15000},
{"200", 20000}}};
OptionsField options_shift{
{UI_POS_X(21), UI_POS_Y(0)},
5,
{{"170", 170},
{"85", 85},
{"200", 200},
{"450", 450},
{"850", 850}}};
Checkbox check_inverted{
{UI_POS_X(0), UI_POS_Y(1)},
10,
"Inverted",
true};
OptionsField options_tone{
{UI_POS_X(25), UI_POS_Y(1)},
5,
{{"CNT", -32000},
{"0", 0},
{"2125", 2125},
{"-2125", -2125},
{"1275", 1275},
{"-1275", -1275}}};
Text text_tones{
{UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
"-"};
Button btn_message{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_HEIGHT(2)},
"Set message"};
Text text_message{
{UI_POS_X(0), UI_POS_Y(5), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
""};
TransmitterView tx_view{
(int16_t)UI_POS_Y_BOTTOM(4),
1000,
0};
RTTYDataMessage rtty_message{};
BaudotCoder baudot_encoder{};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
}};
};
} // namespace ui::external_app::rtty_tx
#endif /*__UI_RTTY_TX_H__*/