mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-31 03:49:04 +00:00
5c2d1d3dfb
Implements MDC-1200 single and double-packet TX as a PPMA external app. Features: - Supports PTT ID, Emergency, Radio Check, Stun/Unstun, Call Alert, Selective Call (1 & 2), Status Request/Response, Remote Monitor, Message - Auto-fills Arg byte on Op selection - AFSK FSK modulation: 1800 Hz mark / 1200 Hz space, 1200 baud - NRZI encoding with Golay FEC per MDC-1200 spec - Tap-to-edit frequency via keypad - Default TX gain of 30 dB - Uses AFSK baseband (same as POCSAG/SAME TX) - All buffers kept off the stack to avoid hard faults in ext app environment * mdc_tx: address PR review feedback - Move MDC helpers into ui::external_app::mdc_tx namespace (drop anonymous namespace) - Use AFSK_TX_SAMPLERATE macro instead of hardcoded 1536000 - Add #include "modems.hpp" explicitly for AFSK_TX_SAMPLERATE - Replace n*8/n*16 pixel positions with UI_POS_X/UI_POS_Y macros - Fix field comments: 'hex bytes' -> 'decimal bytes (0-255)' - Fix TransmitterView frequency_step: 1750000 -> 12500 (12.5kHz NFM step) - Fix MDC preamble: replace leading 0x00 bytes with 0xAA after NRZI encoding stayed 0x00, terminating TX on the first word. 0xAA bytes NRZI-encode to 0xFF (continuous 1800Hz mark tone), which is the standard MDC-1200 preamble and does not trigger the sentinel.
125 lines
4.2 KiB
C++
125 lines
4.2 KiB
C++
/*
|
|
* Copyright (C) 2025 Sarah Rose
|
|
*
|
|
* 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_MDC_TX_H__
|
|
#define __UI_MDC_TX_H__
|
|
|
|
#include "ui.hpp"
|
|
#include "ui_widget.hpp"
|
|
#include "ui_navigation.hpp"
|
|
#include "ui_transmitter.hpp"
|
|
#include "app_settings.hpp"
|
|
#include "radio_state.hpp"
|
|
#include "message.hpp"
|
|
#include "modems.hpp"
|
|
|
|
namespace ui::external_app::mdc_tx {
|
|
|
|
class MdcTxView : public View {
|
|
public:
|
|
MdcTxView(NavigationView& nav);
|
|
~MdcTxView();
|
|
MdcTxView(const MdcTxView&) = delete;
|
|
MdcTxView& operator=(const MdcTxView&) = delete;
|
|
|
|
void focus() override;
|
|
std::string title() const override { return "MDC-1200 TX"; }
|
|
|
|
private:
|
|
NavigationView& nav_;
|
|
|
|
// TxRadioState MUST be declared before SettingsManager
|
|
TxRadioState radio_state_{
|
|
462562500,
|
|
1750000,
|
|
AFSK_TX_SAMPLERATE};
|
|
app_settings::SettingsManager settings_{
|
|
"tx_mdc_tx",
|
|
app_settings::Mode::TX};
|
|
|
|
Labels labels_{{
|
|
{{UI_POS_X(0), UI_POS_Y(0)}, "Op:", ui::Theme::getInstance()->fg_light->foreground},
|
|
{{UI_POS_X(0), UI_POS_Y(1)}, "ID:", ui::Theme::getInstance()->fg_light->foreground},
|
|
{{UI_POS_X(13), UI_POS_Y(1)}, "Arg:", ui::Theme::getInstance()->fg_light->foreground},
|
|
{{UI_POS_X(0), UI_POS_Y(2)}, "Src:", ui::Theme::getInstance()->fg_light->foreground},
|
|
}};
|
|
|
|
// Only one OptionsField allowed per external app
|
|
// Stun=Op 0x2B Arg 0x00, Unstun=Op 0x2B Arg 0x0C (auto-set via on_change)
|
|
OptionsField field_op{
|
|
{UI_POS_X(4), UI_POS_Y(0)},
|
|
14,
|
|
{{"PTT ID", 0x01},
|
|
{"Emergency", 0x00},
|
|
{"Radio Check", 0x63},
|
|
{"Stun", 0x2B},
|
|
{"Unstun", 0xFE}, // sentinel: op=0x2B, arg=0x0C
|
|
{"Call Alert", 0x81},
|
|
{"Sel Call 1", 0x80},
|
|
{"Sel Call 2", 0x82},
|
|
{"Status Req", 0x22},
|
|
{"Status Resp", 0x06},
|
|
{"Rem Monitor", 0x11},
|
|
{"Message", 0x07}}};
|
|
|
|
// Unit ID: two decimal bytes (0-255) combined into a 16-bit ID
|
|
NumberField field_id_hi{{UI_POS_X(4), UI_POS_Y(1)}, 3, {0, 255}, 1, '0'};
|
|
NumberField field_id_lo{{UI_POS_X(7), UI_POS_Y(1)}, 3, {0, 255}, 1, '0'};
|
|
|
|
// Argument byte (decimal 0-255, auto-set by op selection)
|
|
NumberField field_arg{{UI_POS_X(17), UI_POS_Y(1)}, 3, {0, 255}, 1, '0'};
|
|
|
|
// Source ID: two decimal bytes (0-255) combined into a 16-bit ID
|
|
NumberField field_src_hi{{UI_POS_X(4), UI_POS_Y(2)}, 3, {0, 255}, 1, '0'};
|
|
NumberField field_src_lo{{UI_POS_X(7), UI_POS_Y(2)}, 3, {0, 255}, 1, '0'};
|
|
|
|
Text text_status{{0, UI_POS_Y(3), UI_POS_MAXWIDTH, UI_POS_DEFAULT_HEIGHT}, "Ready"};
|
|
|
|
TransmitterView tx_view{
|
|
(int16_t)UI_POS_Y_BOTTOM(4),
|
|
12500,
|
|
0};
|
|
|
|
bool tx_active_{false};
|
|
|
|
// Class-owned scratch buffers — keep large allocations off the stack
|
|
uint8_t tx_stream_[128]{};
|
|
uint8_t lbits_[112]{};
|
|
|
|
void pack_codeword(uint8_t cw[14]);
|
|
int build_single(uint8_t op, uint8_t arg, uint8_t id_hi, uint8_t id_lo);
|
|
int build_double(uint8_t op1, uint8_t arg1, uint8_t id1_hi, uint8_t id1_lo, uint8_t op2, uint8_t arg2, uint8_t id2_hi, uint8_t id2_lo);
|
|
void start_tx();
|
|
void stop_tx();
|
|
void on_tx_progress(uint32_t progress, bool done);
|
|
|
|
MessageHandlerRegistration message_handler_tx_progress{
|
|
Message::ID::TXProgress,
|
|
[this](const Message* const p) {
|
|
const auto msg = *reinterpret_cast<const TXProgressMessage*>(p);
|
|
this->on_tx_progress(msg.progress, msg.done);
|
|
}};
|
|
};
|
|
|
|
} // namespace ui::external_app::mdc_tx
|
|
|
|
#endif
|