mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-08 07:49:04 +00:00
Rtty tx and rx (#2977)
This commit is contained in:
+104
@@ -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
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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__*/
|
||||
Reference in New Issue
Block a user