mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 19:13:00 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bacb31d17 | |||
| cc7e4aec8a | |||
| 15c338e5b3 | |||
| 80bfcd5eb1 | |||
| b96f14762f | |||
| 0feacfa102 | |||
| a1496c1b79 | |||
| 1b73a138b6 | |||
| efcedd9005 | |||
| 86d4b17257 | |||
| f6a437f7fb | |||
| 3db8a5ac5b | |||
| 266a626acf | |||
| 411e2fb93a | |||
| 6ed83c08e3 | |||
| 9929376acf | |||
| 5135b6404d | |||
| 790669c242 | |||
| 4bb8b1882c | |||
| 56702ca7c2 | |||
| 9be4b9596d | |||
| 37c6b35fb7 | |||
| 5cd7a09797 | |||
| a1856b9bb0 | |||
| 29a36c8658 | |||
| 264b1b38ba | |||
| 65e71508ac | |||
| e5546159c5 |
@@ -1 +1 @@
|
||||
v1.7.3
|
||||
v1.7.4
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.7.4
|
||||
v1.8.0
|
||||
|
||||
@@ -8,11 +8,11 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
|
||||
|
||||
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure)*
|
||||
|
||||
# Quick overview
|
||||
# What is this?
|
||||
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, there is an awesome introductory video by [Tech Minds](https://www.youtube.com/channel/UC9a8Z6Sp6eb2s3O79pX5Zvg) available:
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
|
||||
|
||||
[](https://www.youtube.com/watch?v=kjFB58Y1TAo)
|
||||
[](https://www.youtube.com/watch?v=alrFbY5vxt4)
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ set(CPPSRC
|
||||
apps/ais_app.cpp
|
||||
apps/analog_audio_app.cpp
|
||||
apps/analog_tv_app.cpp
|
||||
apps/ble_app.cpp
|
||||
apps/capture_app.cpp
|
||||
apps/ert_app.cpp
|
||||
apps/gps_sim_app.cpp
|
||||
@@ -268,6 +269,7 @@ set(CPPSRC
|
||||
apps/ui_fileman.cpp
|
||||
apps/ui_flash_utility.cpp
|
||||
apps/ui_freqman.cpp
|
||||
apps/ui_fsk_rx.cpp
|
||||
apps/ui_iq_trim.cpp
|
||||
apps/ui_jammer.cpp
|
||||
# apps/ui_keyfob.cpp
|
||||
|
||||
@@ -156,6 +156,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
||||
File f;
|
||||
auto path = get_settings_path(std::string{store_name});
|
||||
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
auto error = f.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
@@ -244,8 +245,7 @@ SettingsManager::SettingsManager(
|
||||
: app_name_{app_name},
|
||||
settings_{},
|
||||
bindings_{},
|
||||
loaded_{false},
|
||||
radio_loaded_{false} {
|
||||
loaded_{false} {
|
||||
settings_.mode = mode;
|
||||
settings_.options = options;
|
||||
|
||||
@@ -253,53 +253,50 @@ SettingsManager::SettingsManager(
|
||||
additional_settings.reserve(17 + additional_settings.size());
|
||||
bindings_ = std::move(additional_settings);
|
||||
|
||||
// Additional settings should always be loaded because apps now rely
|
||||
// on being able to store UI settings, config, etc. The radio settings
|
||||
// are only loaded if the global option has been enabled.
|
||||
// Add the radio setting bindings if either load or save are enabled.
|
||||
if (portapack::persistent_memory::load_app_settings() ||
|
||||
portapack::persistent_memory::save_app_settings()) {
|
||||
// Transmitter model settings.
|
||||
if (flags_enabled(mode, Mode::TX)) {
|
||||
bindings_.emplace_back("tx_frequency"sv, &settings_.tx_frequency);
|
||||
bindings_.emplace_back("tx_amp"sv, &settings_.tx_amp);
|
||||
bindings_.emplace_back("tx_gain"sv, &settings_.tx_gain);
|
||||
bindings_.emplace_back("channel_bandwidth"sv, &settings_.channel_bandwidth);
|
||||
}
|
||||
// Settings should always be loaded because apps now rely
|
||||
// on being able to store UI settings, config, etc.
|
||||
|
||||
// Receiver model settings.
|
||||
if (flags_enabled(mode, Mode::RX)) {
|
||||
bindings_.emplace_back("rx_frequency"sv, &settings_.rx_frequency);
|
||||
bindings_.emplace_back("lna"sv, &settings_.lna);
|
||||
bindings_.emplace_back("vga"sv, &settings_.vga);
|
||||
bindings_.emplace_back("rx_amp"sv, &settings_.rx_amp);
|
||||
bindings_.emplace_back("modulation"sv, &settings_.modulation);
|
||||
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
|
||||
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
|
||||
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
|
||||
bindings_.emplace_back("squelch"sv, &settings_.squelch);
|
||||
}
|
||||
|
||||
// Common model settings.
|
||||
bindings_.emplace_back("baseband_bandwidth"sv, &settings_.baseband_bandwidth);
|
||||
bindings_.emplace_back("sampling_rate"sv, &settings_.sampling_rate);
|
||||
bindings_.emplace_back("step"sv, &settings_.step);
|
||||
bindings_.emplace_back("volume"sv, &settings_.volume);
|
||||
// Transmitter model settings.
|
||||
if (flags_enabled(mode, Mode::TX)) {
|
||||
bindings_.emplace_back("tx_frequency"sv, &settings_.tx_frequency);
|
||||
bindings_.emplace_back("tx_amp"sv, &settings_.tx_amp);
|
||||
bindings_.emplace_back("tx_gain"sv, &settings_.tx_gain);
|
||||
bindings_.emplace_back("channel_bandwidth"sv, &settings_.channel_bandwidth);
|
||||
}
|
||||
|
||||
// Receiver model settings.
|
||||
if (flags_enabled(mode, Mode::RX)) {
|
||||
bindings_.emplace_back("rx_frequency"sv, &settings_.rx_frequency);
|
||||
bindings_.emplace_back("lna"sv, &settings_.lna);
|
||||
bindings_.emplace_back("vga"sv, &settings_.vga);
|
||||
bindings_.emplace_back("rx_amp"sv, &settings_.rx_amp);
|
||||
bindings_.emplace_back("modulation"sv, &settings_.modulation);
|
||||
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
|
||||
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
|
||||
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
|
||||
bindings_.emplace_back("squelch"sv, &settings_.squelch);
|
||||
}
|
||||
|
||||
// Common model settings.
|
||||
bindings_.emplace_back("baseband_bandwidth"sv, &settings_.baseband_bandwidth);
|
||||
bindings_.emplace_back("sampling_rate"sv, &settings_.sampling_rate);
|
||||
bindings_.emplace_back("step"sv, &settings_.step);
|
||||
bindings_.emplace_back("volume"sv, &settings_.volume);
|
||||
|
||||
// RadioState should have initialized default radio parameters before this function;
|
||||
// copy them to settings_ before checking settings .ini file (in case the file doesn't exist
|
||||
// or doesn't include all parameters). Settings in the file can overwrite all, or a subset of parameters.
|
||||
copy_from_radio_model(settings_);
|
||||
|
||||
loaded_ = load_settings(app_name_, bindings_);
|
||||
|
||||
// Only copy to the radio if load was successful.
|
||||
if (loaded_ && portapack::persistent_memory::load_app_settings()) {
|
||||
radio_loaded_ = true;
|
||||
if (loaded_)
|
||||
copy_to_radio_model(settings_);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsManager::~SettingsManager() {
|
||||
// Only save radio settings when the option is enabled.
|
||||
if (portapack::persistent_memory::save_app_settings())
|
||||
copy_from_radio_model(settings_);
|
||||
copy_from_radio_model(settings_);
|
||||
|
||||
save_settings(app_name_, bindings_);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#include "max283x.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
// Folder to store app settings, pmem_fileflag, and date_fileflag
|
||||
#define SETTINGS_DIR u"/SETTINGS"
|
||||
|
||||
// Bring in the string_view literal.
|
||||
using std::literals::operator""sv;
|
||||
|
||||
@@ -170,8 +173,6 @@ class SettingsManager {
|
||||
/* True if settings were successfully loaded from file. */
|
||||
bool loaded() const { return loaded_; }
|
||||
|
||||
/* True if radio settings were successfully loaded from file. */
|
||||
bool radio_loaded() const { return radio_loaded_; }
|
||||
Mode mode() const { return settings_.mode; }
|
||||
|
||||
AppSettings& raw() { return settings_; }
|
||||
@@ -181,7 +182,6 @@ class SettingsManager {
|
||||
AppSettings settings_;
|
||||
SettingBindings bindings_;
|
||||
bool loaded_;
|
||||
bool radio_loaded_;
|
||||
};
|
||||
|
||||
} // namespace app_settings
|
||||
|
||||
@@ -60,11 +60,12 @@ class ACARSAppView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
131550000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_acars.hpp", app_settings::Mode::RX};
|
||||
"rx_acars", app_settings::Mode::RX};
|
||||
|
||||
bool logging{false};
|
||||
uint32_t packet_counter{0};
|
||||
|
||||
@@ -382,9 +382,6 @@ AISAppView::AISAppView(NavigationView& nav)
|
||||
|
||||
recent_entry_detail_view.hidden(true);
|
||||
|
||||
if (!settings_.radio_loaded())
|
||||
receiver_model.set_target_frequency(initial_target_frequency);
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
|
||||
@@ -162,9 +162,8 @@ class AISAppView : public View {
|
||||
std::string title() const override { return "AIS RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 162025000;
|
||||
|
||||
RxRadioState radio_state_{
|
||||
162025000 /* frequency*/,
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
*
|
||||
* 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 "ble_app.hpp"
|
||||
#include "ui_modemsetup.hpp"
|
||||
|
||||
#include "modems.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 modems;
|
||||
|
||||
void BLELogger::log_raw_data(const std::string& data) {
|
||||
log_file.write_entry(data);
|
||||
}
|
||||
|
||||
std::string pad_string_with_spaces(int snakes) {
|
||||
std::string paddedStr(snakes, ' ');
|
||||
return paddedStr;
|
||||
}
|
||||
|
||||
uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
|
||||
uint64_t result = 0;
|
||||
|
||||
// Copy each byte of the MAC address to the corresponding byte in the uint64_t.
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
result |= static_cast<uint64_t>(macAddress[i]) << ((5 - i) * 8);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
template <>
|
||||
void RecentEntriesTable<BleRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
std::string line = to_string_mac_address(entry.packetData.macAddress, 6);
|
||||
|
||||
// Handle spacing for negative sign.
|
||||
uint8_t db_spacing = entry.dbValue > 0 ? 7 : 6;
|
||||
|
||||
// Pushing single digit values down right justified.
|
||||
if (entry.dbValue > 9 || entry.dbValue < -9) {
|
||||
db_spacing--;
|
||||
}
|
||||
|
||||
line += pad_string_with_spaces(db_spacing) + to_string_dec_int(entry.dbValue);
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
}
|
||||
|
||||
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
|
||||
: nav_{nav},
|
||||
entry_{entry} {
|
||||
add_children({&button_done,
|
||||
&labels});
|
||||
|
||||
button_done.on_select = [this](const ui::Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::update_data() {
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
Rect BleRecentEntryDetailView::draw_field(
|
||||
Painter& painter,
|
||||
const Rect& draw_rect,
|
||||
const Style& style,
|
||||
const std::string& label,
|
||||
const std::string& value) {
|
||||
const int label_length_max = 4;
|
||||
|
||||
painter.draw_string(Point{draw_rect.left(), draw_rect.top()}, style, label);
|
||||
painter.draw_string(Point{draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top()}, style, value);
|
||||
|
||||
return {draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height()};
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::paint(Painter& painter) {
|
||||
View::paint(painter);
|
||||
|
||||
const auto s = style();
|
||||
const auto rect = screen_rect();
|
||||
|
||||
auto field_rect = Rect{rect.left(), rect.top() + 16, rect.width(), 16};
|
||||
|
||||
uint8_t type[total_data_lines];
|
||||
uint8_t length[total_data_lines];
|
||||
uint8_t data[total_data_lines][40];
|
||||
|
||||
int currentByte = 0;
|
||||
int currentPacket = 0;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int k = 0;
|
||||
|
||||
for (currentByte = 0; (currentByte < entry_.packetData.dataLen) && (currentPacket < total_data_lines);) {
|
||||
length[currentPacket] = entry_.packetData.data[currentByte++];
|
||||
type[currentPacket] = entry_.packetData.data[currentByte++];
|
||||
|
||||
// Subtract 1 because type is part of the length.
|
||||
for (i = 0; i < length[currentPacket] - 1; i++) {
|
||||
data[currentPacket][i] = entry_.packetData.data[currentByte++];
|
||||
}
|
||||
|
||||
currentPacket++;
|
||||
}
|
||||
|
||||
for (i = 0; i < currentPacket; i++) {
|
||||
uint8_t number_data_lines = ceil((float)(length[i] - 1) / 10.0);
|
||||
uint8_t current_line = 0;
|
||||
std::array<std::string, total_data_lines> data_strings{};
|
||||
|
||||
for (j = 0; (j < (number_data_lines * 10)) && (j < length[i] - 1); j++) {
|
||||
if ((j / 10) != current_line) {
|
||||
current_line++;
|
||||
}
|
||||
|
||||
data_strings[current_line] += to_string_hex(data[i][j], 2);
|
||||
}
|
||||
|
||||
// Read the type back to the total length.
|
||||
field_rect = draw_field(painter, field_rect, s, to_string_hex(length[i]), to_string_hex(type[i]) + pad_string_with_spaces(3) + data_strings[0]);
|
||||
|
||||
if (number_data_lines > 1) {
|
||||
for (k = 1; k < number_data_lines; k++) {
|
||||
if (data_strings[k].empty()) {
|
||||
field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
|
||||
entry_ = entry;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
|
||||
uint64_t freq_hz;
|
||||
|
||||
switch (channel_number) {
|
||||
case 37:
|
||||
freq_hz = 2'402'000'000ull;
|
||||
break;
|
||||
case 38:
|
||||
freq_hz = 2'426'000'000ull;
|
||||
break;
|
||||
case 39:
|
||||
freq_hz = 2'480'000'000ull;
|
||||
break;
|
||||
case 0 ... 10:
|
||||
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
|
||||
break;
|
||||
case 11 ... 36:
|
||||
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
|
||||
break;
|
||||
default:
|
||||
freq_hz = UINT64_MAX;
|
||||
}
|
||||
|
||||
return freq_hz;
|
||||
}
|
||||
|
||||
void BLERxView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
BLERxView::BLERxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
|
||||
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&options_channel,
|
||||
&field_frequency,
|
||||
&check_log,
|
||||
&label_sort,
|
||||
&options_sort,
|
||||
&recent_entries_view,
|
||||
&recent_entry_detail_view});
|
||||
|
||||
recent_entry_detail_view.hidden(true);
|
||||
|
||||
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
|
||||
nav_.push<BleRecentEntryDetailView>(entry);
|
||||
};
|
||||
|
||||
field_frequency.set_step(0);
|
||||
|
||||
check_log.set_value(logging);
|
||||
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
str_log = "";
|
||||
logging = v;
|
||||
};
|
||||
|
||||
options_channel.on_change = [this](size_t, int32_t i) {
|
||||
field_frequency.set_value(get_freq_by_channel_number(i));
|
||||
channel_number = i;
|
||||
|
||||
baseband::set_btle(channel_number);
|
||||
};
|
||||
|
||||
options_sort.on_change = [this](size_t, int32_t i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
sortEntriesBy(
|
||||
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
|
||||
break;
|
||||
case 1:
|
||||
sortEntriesBy(
|
||||
recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true);
|
||||
break;
|
||||
case 2:
|
||||
sortEntriesBy(
|
||||
recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
|
||||
options_channel.set_selected_index(0, true);
|
||||
options_sort.set_selected_index(0, true);
|
||||
|
||||
logger = std::make_unique<BLELogger>();
|
||||
|
||||
if (logger)
|
||||
logger->append(LOG_ROOT_DIR "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
baseband::set_btle(channel_number);
|
||||
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
void BLERxView::on_data(BlePacketData* packet) {
|
||||
std::string str_console = "";
|
||||
|
||||
if (!logging) {
|
||||
str_log = "";
|
||||
}
|
||||
|
||||
switch ((ADV_PDU_TYPE)packet->type) {
|
||||
case ADV_IND:
|
||||
str_console += "ADV_IND";
|
||||
break;
|
||||
case ADV_DIRECT_IND:
|
||||
str_console += "ADV_DIRECT_IND";
|
||||
break;
|
||||
case ADV_NONCONN_IND:
|
||||
str_console += "ADV_NONCONN_IND";
|
||||
break;
|
||||
case SCAN_REQ:
|
||||
str_console += "SCAN_REQ";
|
||||
break;
|
||||
case SCAN_RSP:
|
||||
str_console += "SCAN_RSP";
|
||||
break;
|
||||
case CONNECT_REQ:
|
||||
str_console += "CONNECT_REQ";
|
||||
break;
|
||||
case ADV_SCAN_IND:
|
||||
str_console += "ADV_SCAN_IND";
|
||||
break;
|
||||
case RESERVED0:
|
||||
case RESERVED1:
|
||||
case RESERVED2:
|
||||
case RESERVED3:
|
||||
case RESERVED4:
|
||||
case RESERVED5:
|
||||
case RESERVED6:
|
||||
case RESERVED7:
|
||||
case RESERVED8:
|
||||
str_console += "RESERVED";
|
||||
break;
|
||||
default:
|
||||
str_console += "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
||||
// str_console += to_string_dec_uint(value);
|
||||
|
||||
str_console += " Len:";
|
||||
str_console += to_string_dec_uint(packet->size);
|
||||
|
||||
str_console += "\n";
|
||||
|
||||
str_console += "Mac:";
|
||||
str_console += to_string_mac_address(packet->macAddress, 6);
|
||||
|
||||
str_console += "\n";
|
||||
str_console += "Data:";
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < packet->dataLen; i++) {
|
||||
str_console += " " + to_string_hex(packet->data[i], 2);
|
||||
}
|
||||
|
||||
str_console += "\n";
|
||||
|
||||
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
|
||||
|
||||
// Start of Packet stuffing.
|
||||
// Masking off the top 2 bytes to avoid invalid keys.
|
||||
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
|
||||
|
||||
entry.dbValue = packet->max_dB;
|
||||
entry.timestamp = to_string_timestamp(rtc_time::now());
|
||||
|
||||
entry.packetData.type = packet->type;
|
||||
entry.packetData.size = packet->size;
|
||||
entry.packetData.dataLen = packet->dataLen;
|
||||
|
||||
entry.packetData.macAddress[0] = packet->macAddress[0];
|
||||
entry.packetData.macAddress[1] = packet->macAddress[1];
|
||||
entry.packetData.macAddress[2] = packet->macAddress[2];
|
||||
entry.packetData.macAddress[3] = packet->macAddress[3];
|
||||
entry.packetData.macAddress[4] = packet->macAddress[4];
|
||||
entry.packetData.macAddress[5] = packet->macAddress[5];
|
||||
|
||||
for (int i = 0; i < packet->dataLen; i++) {
|
||||
entry.packetData.data[i] = packet->data[i];
|
||||
}
|
||||
|
||||
// entry.update(packet);
|
||||
|
||||
switch (options_sort.selected_index()) {
|
||||
case 0:
|
||||
sortEntriesBy(
|
||||
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
|
||||
break;
|
||||
case 1:
|
||||
sortEntriesBy(
|
||||
recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true);
|
||||
break;
|
||||
case 2:
|
||||
sortEntriesBy(
|
||||
recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
recent_entries_view.set_dirty();
|
||||
|
||||
// TODO: Crude hack, should be a more formal listener arrangement...
|
||||
if (entry.key() == recent_entry_detail_view.entry().key()) {
|
||||
recent_entry_detail_view.set_entry(entry);
|
||||
}
|
||||
|
||||
// Log at End of Packet.
|
||||
if (logger && logging) {
|
||||
logger->log_raw_data(str_console);
|
||||
}
|
||||
}
|
||||
|
||||
void BLERxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
recent_entries_view.set_parent_rect(content_rect);
|
||||
recent_entry_detail_view.set_parent_rect(content_rect);
|
||||
}
|
||||
|
||||
BLERxView::~BLERxView() {
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
// BleRecentEntry
|
||||
// void BleRecentEntry::update(const BlePacketData * packet)
|
||||
// {
|
||||
|
||||
// }
|
||||
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
*
|
||||
* 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 __BLE_APP_H__
|
||||
#define __BLE_APP_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
class BLELogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void log_raw_data(const std::string& data);
|
||||
|
||||
private:
|
||||
LogFile log_file{};
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
typedef enum {
|
||||
ADV_IND = 0,
|
||||
ADV_DIRECT_IND = 1,
|
||||
ADV_NONCONN_IND = 2,
|
||||
SCAN_REQ = 3,
|
||||
SCAN_RSP = 4,
|
||||
CONNECT_REQ = 5,
|
||||
ADV_SCAN_IND = 6,
|
||||
RESERVED0 = 7,
|
||||
RESERVED1 = 8,
|
||||
RESERVED2 = 9,
|
||||
RESERVED3 = 10,
|
||||
RESERVED4 = 11,
|
||||
RESERVED5 = 12,
|
||||
RESERVED6 = 13,
|
||||
RESERVED7 = 14,
|
||||
RESERVED8 = 15
|
||||
} ADV_PDU_TYPE;
|
||||
|
||||
struct BleRecentEntry {
|
||||
using Key = uint64_t;
|
||||
|
||||
static constexpr Key invalid_key = 0xffffffff;
|
||||
|
||||
uint64_t macAddress;
|
||||
int dbValue;
|
||||
BlePacketData packetData;
|
||||
std::string timestamp;
|
||||
|
||||
BleRecentEntry()
|
||||
: BleRecentEntry{0} {
|
||||
}
|
||||
|
||||
BleRecentEntry(
|
||||
const uint64_t macAddress)
|
||||
: macAddress{macAddress},
|
||||
dbValue{},
|
||||
packetData{},
|
||||
timestamp{} {
|
||||
}
|
||||
|
||||
Key key() const {
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
// void update(const BlePacketData * packet);
|
||||
};
|
||||
|
||||
using BleRecentEntries = RecentEntries<BleRecentEntry>;
|
||||
using BleRecentEntriesView = RecentEntriesView<BleRecentEntries>;
|
||||
|
||||
class BleRecentEntryDetailView : public View {
|
||||
public:
|
||||
BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry);
|
||||
|
||||
void set_entry(const BleRecentEntry& new_entry);
|
||||
const BleRecentEntry& entry() const { return entry_; };
|
||||
|
||||
void update_data();
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
BleRecentEntry entry_{};
|
||||
|
||||
static constexpr uint8_t total_data_lines{5};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Len", Color::light_grey()},
|
||||
{{5 * 8, 0 * 16}, "Type", Color::light_grey()},
|
||||
{{10 * 8, 0 * 16}, "Value", Color::light_grey()},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
{125, 224, 96, 24},
|
||||
"Done"};
|
||||
|
||||
bool send_updates{false};
|
||||
|
||||
Rect draw_field(
|
||||
Painter& painter,
|
||||
const Rect& draw_rect,
|
||||
const Style& style,
|
||||
const std::string& label,
|
||||
const std::string& value);
|
||||
};
|
||||
|
||||
class BLERxView : public View {
|
||||
public:
|
||||
BLERxView(NavigationView& nav);
|
||||
~BLERxView();
|
||||
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
void paint(Painter&) override{};
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "BLE RX"; };
|
||||
|
||||
private:
|
||||
void on_data(BlePacketData* packetData);
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
2402000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
app_settings::SettingsManager settings_{
|
||||
"BLE Rx", app_settings::Mode::RX};
|
||||
|
||||
uint8_t console_color{0};
|
||||
uint32_t prev_value{0};
|
||||
uint8_t channel_number = 37;
|
||||
|
||||
static constexpr auto header_height = 12 + 2 * 16;
|
||||
|
||||
OptionsField options_channel{
|
||||
{0 * 8, 0 * 8},
|
||||
5,
|
||||
{{"Ch.37 ", 37},
|
||||
{"Ch.38", 38},
|
||||
{"Ch.39", 39}}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{6 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{16 * 8, 0 * 16}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{18 * 8, 0 * 16}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{21 * 8, 0 * 16}};
|
||||
|
||||
RSSI rssi{
|
||||
{24 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
Channel channel{
|
||||
{24 * 8, 5, 6 * 8, 4}};
|
||||
|
||||
Checkbox check_log{
|
||||
{0 * 8, 3 * 8},
|
||||
3,
|
||||
"Log",
|
||||
true};
|
||||
|
||||
Labels label_sort{
|
||||
{{6 * 8, 3 * 8}, "Sort:", Color::light_grey()}};
|
||||
|
||||
OptionsField options_sort{
|
||||
{12 * 8, 3 * 8},
|
||||
6,
|
||||
{{"MAC", 0},
|
||||
{"dB", 1},
|
||||
{"Recent", 2}}};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, 240, 240}};
|
||||
|
||||
std::string str_log{""};
|
||||
bool logging{false};
|
||||
|
||||
std::unique_ptr<BLELogger> logger{};
|
||||
|
||||
// const RecentEntriesColumns columns{{{"Source", 9},
|
||||
// {"Loc", 6},
|
||||
// {"Hits", 4},
|
||||
// {"Time", 8}}};
|
||||
|
||||
BleRecentEntries recent{};
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Mac Address", 20},
|
||||
{"dB", 20},
|
||||
}};
|
||||
|
||||
BleRecentEntry entry_{};
|
||||
BleRecentEntriesView recent_entries_view{columns, recent};
|
||||
BleRecentEntryDetailView recent_entry_detail_view{nav_, entry_};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::BlePacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const BLEPacketMessage*>(p);
|
||||
this->on_data(message->packet);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_AFSK_RX_H__*/
|
||||
@@ -110,9 +110,6 @@ ERTAppView::ERTAppView(NavigationView&) {
|
||||
&recent_entries_view,
|
||||
});
|
||||
|
||||
if (!settings_.radio_loaded())
|
||||
receiver_model.set_target_frequency(initial_target_frequency);
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
logger = std::make_unique<ERTLogger>();
|
||||
|
||||
@@ -123,12 +123,11 @@ class ERTAppView : public View {
|
||||
std::string title() const override { return "ERT RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 911600000;
|
||||
|
||||
ERTRecentEntries recent{};
|
||||
std::unique_ptr<ERTLogger> logger{};
|
||||
|
||||
RxRadioState radio_state_{
|
||||
911600000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
4194304 /* sampling rate */};
|
||||
app_settings::SettingsManager settings_{
|
||||
|
||||
@@ -74,7 +74,10 @@ void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
auto duration = ms_duration(file_size, transmitter_model.sampling_rate(), 2);
|
||||
text_duration.set(to_string_time_ms(duration));
|
||||
|
||||
button_play.focus();
|
||||
// TODO: fix in UI framework with 'try_focus()'?
|
||||
// Hack around focus getting called by ctor before parent is set.
|
||||
if (parent())
|
||||
button_play.focus();
|
||||
}
|
||||
|
||||
void GpsSimAppView::on_tx_progress(const uint32_t progress) {
|
||||
@@ -173,13 +176,6 @@ GpsSimAppView::GpsSimAppView(
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
transmitter_model.set_baseband_bandwidth(15'000'000); // GPS L1 signal use to have wide band spectrum, still with lobule energy -30dB's at + - 15 Mhz
|
||||
|
||||
if (!settings_.radio_loaded()) {
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
transmitter_model.set_sampling_rate(2600000);
|
||||
}
|
||||
|
||||
field_frequency.set_step(5000);
|
||||
|
||||
button_play.on_select = [this](ImageButton&) {
|
||||
|
||||
@@ -51,21 +51,17 @@ class GpsSimAppView : public View {
|
||||
std::string title() const override { return "GPS Sim TX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 1575420000;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
3000000 /* bandwidth */,
|
||||
500000 /* sampling rate */
|
||||
TxRadioState radio_state_{
|
||||
1575420000 /* frequency */,
|
||||
15000000 /* bandwidth */,
|
||||
2600000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_gps", app_settings::Mode::TX};
|
||||
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
|
||||
int32_t tx_gain{47};
|
||||
bool rf_amp{true}; // aux private var to store temporal, same as Replay App rf_amp user selection.
|
||||
static constexpr uint32_t baseband_bandwidth = 3000000; // filter bandwidth
|
||||
const size_t read_size{16384};
|
||||
const size_t buffer_count{3};
|
||||
|
||||
|
||||
@@ -238,6 +238,10 @@ void LGEView::start_tx() {
|
||||
tx_view.set_dirty();
|
||||
}
|
||||
|
||||
/* By experimental test, previous fw 1.7.4 seems to have setting , tx LPF = 5Mhz
|
||||
This LGE - seems to be for controlling a "laser tag equipment" (Havoc app) , modulated FSK , low bauds,
|
||||
and using max GUI fm dev 150k , we are still in NBFM, let's reduce slightly original TX LPF 5M ->2M5*/
|
||||
transmitter_model.set_baseband_bandwidth(2'500'000);
|
||||
transmitter_model.enable();
|
||||
|
||||
chThdSleep(100);
|
||||
|
||||
@@ -51,6 +51,7 @@ class LGEView : public View {
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
868067000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -114,9 +114,6 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
||||
? FILTER_NONE
|
||||
: FILTER_DROP;
|
||||
}
|
||||
if (!app_settings_.radio_loaded()) {
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
}
|
||||
|
||||
logger.append(LOG_ROOT_DIR "/POCSAG.TXT");
|
||||
|
||||
|
||||
@@ -197,14 +197,17 @@ class POCSAGAppView : public View {
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 466'175'000;
|
||||
bool logging() const { return settings_.enable_logging; };
|
||||
bool logging_raw() const { return settings_.enable_raw_log; };
|
||||
bool hide_bad_data() const { return settings_.hide_bad_data; };
|
||||
bool hide_addr_only() const { return settings_.hide_addr_only; };
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
RxRadioState radio_state_{
|
||||
466175000 /* frequency*/,
|
||||
max283x::filter::bandwidth_minimum /* bandwidth */,
|
||||
3072000 /* sampling rate */
|
||||
};
|
||||
|
||||
// Settings
|
||||
POCSAGSettings settings_{};
|
||||
|
||||
@@ -120,7 +120,6 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // the Minimum TX LPF 1M75 is fine.
|
||||
transmitter_model.enable();
|
||||
|
||||
tx_view.set_transmitting(true);
|
||||
|
||||
@@ -50,6 +50,7 @@ class SoundBoardView : public View {
|
||||
|
||||
private:
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -157,9 +157,6 @@ TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
&field_vga,
|
||||
&recent_entries_view});
|
||||
|
||||
if (!settings_.radio_loaded())
|
||||
receiver_model.set_target_frequency(initial_target_frequency);
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
options_band.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
|
||||
@@ -102,9 +102,8 @@ class TPMSAppView : public View {
|
||||
std::string title() const override { return "TPMS RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 315000000;
|
||||
|
||||
RxRadioState radio_state_{
|
||||
315000000 /* frequency*/,
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */};
|
||||
app_settings::SettingsManager settings_{
|
||||
@@ -135,7 +134,7 @@ class TPMSAppView : public View {
|
||||
3,
|
||||
{
|
||||
{"315", 315000000},
|
||||
{"433", 433920000},
|
||||
{"434", 433920000},
|
||||
}};
|
||||
|
||||
OptionsField options_pressure{
|
||||
|
||||
@@ -37,6 +37,7 @@ void AboutView::update() {
|
||||
break;
|
||||
case 2:
|
||||
console.writeln("NotherNgineer,zxkmm,u-foka");
|
||||
console.writeln("Netro");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool ac_details_view_active{false};
|
||||
|
||||
template <>
|
||||
void RecentEntriesTable<AircraftRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
@@ -182,6 +184,7 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
|
||||
break;
|
||||
}
|
||||
button_close.on_select = [&nav](Button&) {
|
||||
ac_details_view_active = false;
|
||||
nav.pop();
|
||||
};
|
||||
};
|
||||
@@ -217,6 +220,7 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
|
||||
}
|
||||
|
||||
ADSBRxDetailsView::~ADSBRxDetailsView() {
|
||||
ac_details_view_active = false;
|
||||
on_close_();
|
||||
}
|
||||
|
||||
@@ -260,6 +264,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
|
||||
|
||||
button_aircraft_details.on_select = [this, &nav](Button&) {
|
||||
ac_details_view_active = true;
|
||||
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(entry_copy, [this]() { send_updates = false; });
|
||||
send_updates = false;
|
||||
};
|
||||
@@ -433,6 +438,11 @@ void ADSBRxView::updateDetailsAndMap(int ageStep) {
|
||||
ui::GeoMarker marker;
|
||||
bool storeNewMarkers = false;
|
||||
|
||||
// NB: Temporarily pausing updates in rtc_timer_tick context when viewing AC Details screen (kludge for some Guru faults)
|
||||
// TODO: More targeted blocking of updates in rtc_timer_tick when ADSB processes are running
|
||||
if (ac_details_view_active)
|
||||
return;
|
||||
|
||||
// Sort and truncate the entries, grouped, newest group first
|
||||
sort_entries_by_state();
|
||||
truncate_entries(recent);
|
||||
@@ -511,7 +521,6 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
||||
|
||||
baseband::set_adsb();
|
||||
|
||||
receiver_model.set_target_frequency(1'090'000'000);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
|
||||
@@ -342,6 +342,7 @@ class ADSBRxView : public View {
|
||||
|
||||
private:
|
||||
RxRadioState radio_state_{
|
||||
1090000000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
2000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::SpectrumAnalysis};
|
||||
|
||||
@@ -299,7 +299,6 @@ void ADSBTxView::start_tx() {
|
||||
/* Already tested , with SDR Angel + another Hackrf RX and dump1090 + SDR RLT. Final conclusion is TX LPF 6 Mhz is
|
||||
* the best settings to fulfill ADSB transponder spectrum mask requirements (<=-20 dB's at +-7Mhz , <=-40 dB's at +-23Mhz )
|
||||
* and not showing any ADSB data decoding degradation.*/
|
||||
transmitter_model.set_baseband_bandwidth(6'000'000); // best settings for ADSB TX.
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_adsb();
|
||||
|
||||
@@ -203,7 +203,8 @@ class ADSBTxView : public View {
|
||||
};*/
|
||||
|
||||
TxRadioState radio_state_{
|
||||
10000000 /* bandwidth */,
|
||||
1090000000 /* frequency */,
|
||||
6000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
|
||||
@@ -190,7 +190,11 @@ class APRSRxView : public View {
|
||||
bool reset_console = false;
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
RxRadioState radio_state_{
|
||||
144390000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
3072000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_aprs", app_settings::Mode::RX};
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ void APRSTXView::start_tx() {
|
||||
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
|
||||
// text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
|
||||
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // APRS Automatic Packet Reporting System (APRS).AFSK with NBFM , max BW= 12k5 , then TX LPF min 1M75
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_afsk_data(
|
||||
|
||||
@@ -46,6 +46,7 @@ class APRSTXView : public View {
|
||||
|
||||
private:
|
||||
TxRadioState radio_state_{
|
||||
144390000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
AFSK_TX_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -54,7 +54,6 @@ BTLERxView::BTLERxView(NavigationView& nav)
|
||||
&console});
|
||||
|
||||
// Auto-configure modem for LCR RX (TODO: remove later)
|
||||
field_frequency.set_value(2426000000);
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
|
||||
serial_format_t serial_format;
|
||||
@@ -71,7 +70,7 @@ BTLERxView::BTLERxView(NavigationView& nav)
|
||||
};
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
baseband::set_btle(persistent_memory::modem_baudrate(), 8, 0, false);
|
||||
baseband::set_btle(channel_number);
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
|
||||
@@ -50,6 +50,7 @@ class BTLERxView : public View {
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
2426000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
@@ -59,6 +60,8 @@ class BTLERxView : public View {
|
||||
uint8_t console_color{0};
|
||||
uint32_t prev_value{0};
|
||||
|
||||
static constexpr uint8_t channel_number = 37;
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
LNAGainField field_lna{
|
||||
|
||||
@@ -67,7 +67,6 @@ void CoasterPagerView::generate_frame() {
|
||||
void CoasterPagerView::start_tx() {
|
||||
generate_frame();
|
||||
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // AFSK narrowband ,low baud, max FM dev 150khz , std 10khz. TX LPF=1M75 the min.
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_fsk_data(19 * 8, 2280000 / 1000, 5000, 32);
|
||||
|
||||
@@ -52,11 +52,12 @@ class CoasterPagerView : public View {
|
||||
tx_modes tx_mode = IDLE;
|
||||
|
||||
TxRadioState radio_state_{
|
||||
433920000, /* frequency */
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_coaster", app_settings::Mode::TX};
|
||||
"tx_burger", app_settings::Mode::TX};
|
||||
|
||||
void start_tx();
|
||||
void generate_frame();
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
@@ -400,7 +401,6 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
add_items({
|
||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
|
||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
|
||||
//{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }}, // temporarily disabled to conserve ROM space
|
||||
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { stack_dump(); }},
|
||||
{"Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
|
||||
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
|
||||
@@ -410,6 +410,11 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
|
||||
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
|
||||
});
|
||||
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav)) {
|
||||
add_item(gridItem);
|
||||
};
|
||||
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
@@ -469,41 +474,6 @@ uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
|
||||
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
|
||||
}
|
||||
|
||||
/* DebugFontsView *******************************************************/
|
||||
|
||||
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
|
||||
auto char_width{font_style->font.char_width()};
|
||||
auto char_height{font_style->font.line_height()};
|
||||
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
|
||||
uint16_t line_pos{y_offset};
|
||||
|
||||
painter.draw_string({0, y_offset}, *font_style, font_name);
|
||||
|
||||
// Displaying ASCII+extended characters from 0x20 to 0xFF
|
||||
for (uint8_t c = 0; c <= 0xDF; c++) {
|
||||
line_pos = y_offset + ((c / cpl) + 2) * char_height;
|
||||
|
||||
if ((c % cpl) == 0)
|
||||
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
|
||||
|
||||
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
|
||||
}
|
||||
|
||||
return line_pos + char_height;
|
||||
}
|
||||
|
||||
void DebugFontsView::paint(Painter& painter) {
|
||||
int16_t line_pos;
|
||||
|
||||
line_pos = display_font(painter, 32, &Styles::white, "Fixed 8x16");
|
||||
display_font(painter, line_pos + 16, &Styles::white_small, "Fixed 5x8");
|
||||
}
|
||||
|
||||
DebugFontsView::DebugFontsView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
/* DebugScreenTest ****************************************************/
|
||||
|
||||
DebugScreenTest::DebugScreenTest(NavigationView& nav)
|
||||
|
||||
@@ -310,17 +310,6 @@ class DebugPmemView : public View {
|
||||
uint32_t registers_widget_feed(const size_t register_number);
|
||||
};
|
||||
|
||||
class DebugFontsView : public View {
|
||||
public:
|
||||
DebugFontsView(NavigationView& nav);
|
||||
void paint(Painter& painter) override;
|
||||
std::string title() const override { return "Fonts"; };
|
||||
|
||||
private:
|
||||
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
|
||||
NavigationView& nav_;
|
||||
};
|
||||
|
||||
class DebugScreenTest : public View {
|
||||
public:
|
||||
DebugScreenTest(NavigationView& nav);
|
||||
|
||||
@@ -92,7 +92,9 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
|
||||
&text_info_line_5,
|
||||
&text_info_line_6,
|
||||
&text_info_line_7,
|
||||
&text_info_line_8});
|
||||
&text_info_line_8,
|
||||
&text_info_line_9,
|
||||
&text_info_line_10});
|
||||
}
|
||||
|
||||
void DfuMenu2::paint(Painter& painter) {
|
||||
@@ -103,10 +105,12 @@ void DfuMenu2::paint(Painter& painter) {
|
||||
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
|
||||
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
|
||||
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
|
||||
text_info_line_8.set("");
|
||||
text_info_line_8.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
|
||||
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
|
||||
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 8 + 2;
|
||||
constexpr auto lines = 10 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
|
||||
@@ -77,17 +77,19 @@ class DfuMenu2 : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 8 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Receiver"};
|
||||
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 14 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Radio Settings"};
|
||||
|
||||
Labels labels{
|
||||
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "Tgt freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "Bandwidt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "Sampl Rt:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "", Color::dark_cyan()}};
|
||||
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
|
||||
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
@@ -97,6 +99,8 @@ class DfuMenu2 : public View {
|
||||
Text text_info_line_6{{14 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_7{{14 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -269,6 +269,9 @@ void EncodersView::start_tx(const bool scan) {
|
||||
repeat_index = 1;
|
||||
update_progress();
|
||||
|
||||
/* Setting TX LPF 1M75 in this TX OOK App , We got same results as fw 1.7.4
|
||||
* Looking max BW of this app, we tested , Selecting OOK type 145026 with CLK 455K and max DEV. 150k,
|
||||
* and we got BW +-2Mhz , with that TX LPF 1M75, it is fine.*/
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_ook_data(
|
||||
|
||||
@@ -185,6 +185,7 @@ class EncodersView : public View {
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
OOK_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* 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_fsk_rx.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
void FskRxLogger::log_raw_data(const std::string& data, const uint32_t frequency) {
|
||||
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz";
|
||||
|
||||
// // Raw hex dump of all the codewords
|
||||
// for (size_t c = 0; c < 16; c++)
|
||||
// entry += to_string_hex(packet[c], 8) + " ";
|
||||
|
||||
log_file.write_entry(data + entry);
|
||||
}
|
||||
|
||||
void FskRxLogger::log_decoded(Timestamp timestamp, const std::string& text) {
|
||||
log_file.write_entry(timestamp, text);
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
// Console View
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
FskRxAppConsoleView::FskRxAppConsoleView(NavigationView& nav, Rect parent_rect)
|
||||
: View(parent_rect), nav_{nav} {
|
||||
add_child(&console);
|
||||
};
|
||||
|
||||
void FskRxAppConsoleView::on_packet(uint32_t value, bool is_data) {
|
||||
if (is_data) {
|
||||
console.write(to_string_dec_uint(value) + " ");
|
||||
}
|
||||
}
|
||||
|
||||
void FskRxAppConsoleView::on_show() {
|
||||
hidden(false);
|
||||
}
|
||||
|
||||
void FskRxAppConsoleView::on_hide() {
|
||||
hidden(true);
|
||||
}
|
||||
|
||||
FskRxAppConsoleView::~FskRxAppConsoleView() {
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
// Spectrum View
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
FskRxAppView::FskRxAppView(NavigationView& nav, Rect parent_rect)
|
||||
: View(parent_rect), nav_{nav} {
|
||||
add_child(&waterfall);
|
||||
hidden(true);
|
||||
}
|
||||
|
||||
FskRxAppView::~FskRxAppView() {
|
||||
}
|
||||
|
||||
void FskRxAppView::focus() {
|
||||
}
|
||||
|
||||
void FskRxAppView::on_show() {
|
||||
hidden(false);
|
||||
waterfall.start();
|
||||
}
|
||||
|
||||
void FskRxAppView::on_hide() {
|
||||
hidden(true);
|
||||
waterfall.stop();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
// Base View
|
||||
//---------------------------------------------------------------------------------------------------------------
|
||||
FskxRxMainView::FskxRxMainView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&tab_view,
|
||||
&view_data,
|
||||
&view_stream,
|
||||
&labels,
|
||||
&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_frequency,
|
||||
&deviation_frequency,
|
||||
&record_view});
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_fskrx);
|
||||
|
||||
// DEBUG
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
|
||||
deviation_frequency.on_change = [this](rf::Frequency f) {
|
||||
refresh_ui(f);
|
||||
};
|
||||
|
||||
// Set initial sampling rate
|
||||
/* Bandwidth of 2FSK is 2 * Deviation */
|
||||
record_view.set_sampling_rate(initial_deviation * 2);
|
||||
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
deviation_frequency.set_value(initial_deviation);
|
||||
|
||||
logger.append(LOG_ROOT_DIR "/FSKRX.TXT");
|
||||
|
||||
baseband::set_fsk(initial_deviation);
|
||||
|
||||
audio::output::start();
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
void FskxRxMainView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
|
||||
if (logging()) {
|
||||
logger.log_decoded(timestamp, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
void FskxRxMainView::refresh_ui(rf::Frequency deviationHz) {
|
||||
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
|
||||
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
|
||||
/* Bandwidth of 2FSK is 2 * Deviation */
|
||||
auto sample_rate = deviationHz * 2;
|
||||
|
||||
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
|
||||
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
|
||||
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
|
||||
|
||||
if (!view_stream.hidden()) {
|
||||
view_stream.waterfall.stop();
|
||||
}
|
||||
|
||||
// record_view determines the correct oversampling to apply and returns the actual sample rate.
|
||||
// NB: record_view is what actually updates proc_capture baseband settings.
|
||||
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
|
||||
|
||||
// Update the radio model with the actual sampling rate.
|
||||
receiver_model.set_sampling_rate(actual_sample_rate);
|
||||
|
||||
// Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
|
||||
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
|
||||
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
|
||||
|
||||
if (!view_stream.hidden()) {
|
||||
view_stream.waterfall.start();
|
||||
}
|
||||
}
|
||||
|
||||
void FskxRxMainView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
void FskxRxMainView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
|
||||
ui::Rect waterfall_rect{0, 0, new_parent_rect.width(), new_parent_rect.height() - header_height};
|
||||
view_stream.waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
FskxRxMainView::~FskxRxMainView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
} /* namespace ui */
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2018 Furrtek
|
||||
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
|
||||
*
|
||||
* 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_FSK_RX_H__
|
||||
#define __UI_FSK_RX_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "pocsag_app.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
class FskRxLogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void log_raw_data(const std::string& data, const uint32_t frequency);
|
||||
void log_decoded(Timestamp timestamp, const std::string& text);
|
||||
|
||||
private:
|
||||
LogFile log_file{};
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
class FskRxAppConsoleView : public View {
|
||||
public:
|
||||
FskRxAppConsoleView(NavigationView& nav, Rect parent_rec);
|
||||
~FskRxAppConsoleView();
|
||||
|
||||
std::string title() const override { return "FSK RX Data"; };
|
||||
|
||||
void on_packet(uint32_t value, bool is_data);
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Console console{
|
||||
{0, 0, 240, 224}};
|
||||
};
|
||||
|
||||
class FskRxAppView : public View {
|
||||
public:
|
||||
FskRxAppView(NavigationView& nav, Rect parent_rect);
|
||||
~FskRxAppView();
|
||||
|
||||
void focus() override;
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
spectrum::WaterfallView waterfall{};
|
||||
|
||||
std::string title() const override { return "FSK RX Stream"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
};
|
||||
|
||||
class FskxRxMainView : public View {
|
||||
public:
|
||||
FskxRxMainView(NavigationView& nav);
|
||||
~FskxRxMainView();
|
||||
|
||||
void focus() override;
|
||||
void set_parent_rect(const Rect new_parent_rect) override;
|
||||
|
||||
std::string title() const override { return "FSK RX"; };
|
||||
|
||||
private:
|
||||
static constexpr uint32_t initial_target_frequency = 902'075'000;
|
||||
static constexpr ui::Dim header_height = (5 * 16);
|
||||
|
||||
uint32_t initial_deviation{3750};
|
||||
|
||||
bool logging() const { return false; };
|
||||
bool logging_raw() const { return false; };
|
||||
|
||||
NavigationView& nav_;
|
||||
Rect view_rect = {0, header_height, 240, 224};
|
||||
|
||||
FskRxAppView view_stream{nav_, view_rect};
|
||||
FskRxAppConsoleView view_data{nav_, view_rect};
|
||||
|
||||
TabView tab_view{
|
||||
{"Data", Color::yellow(), &view_data},
|
||||
{"Stream", Color::cyan(), &view_stream}};
|
||||
|
||||
void refresh_ui(rf::Frequency f);
|
||||
void on_packet(uint32_t value, bool is_data);
|
||||
void handle_decoded(Timestamp timestamp, const std::string& prefix);
|
||||
|
||||
uint32_t last_address = 0;
|
||||
FskRxLogger logger{};
|
||||
uint16_t packet_count = 0;
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 4 * 8},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{11 * 8, 2 * 16}};
|
||||
|
||||
LNAGainField field_lna{
|
||||
{13 * 8, 2 * 16}};
|
||||
|
||||
VGAGainField field_vga{
|
||||
{16 * 8, 2 * 16}};
|
||||
|
||||
RSSI rssi{
|
||||
{19 * 8 - 4, 35, 6 * 8, 4}};
|
||||
|
||||
Channel channel{
|
||||
{19 * 8 - 4, 40, 6 * 8, 4}};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "Deviation:", Color::light_grey()},
|
||||
};
|
||||
|
||||
FrequencyField deviation_frequency{
|
||||
{10 * 8, 3 * 16},
|
||||
{3750, 500000},
|
||||
};
|
||||
|
||||
// DEBUG
|
||||
RecordView record_view{
|
||||
{0 * 8, 4 * 16, 30 * 8, 1 * 16},
|
||||
u"FSKRX_????.C16",
|
||||
u"FSKRX",
|
||||
RecordView::FileType::RawS16,
|
||||
16384,
|
||||
3};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AFSKData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const AFSKDataMessage*>(p);
|
||||
this->view_data.on_packet(message->value, message->is_data);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__POCSAG_APP_H__*/
|
||||
@@ -40,6 +40,7 @@ IQTrimView::IQTrimView(NavigationView& nav)
|
||||
&text_samples,
|
||||
&text_max,
|
||||
&field_cutoff,
|
||||
&field_amplify,
|
||||
&button_trim,
|
||||
});
|
||||
|
||||
@@ -72,6 +73,11 @@ IQTrimView::IQTrimView(NavigationView& nav)
|
||||
refresh_ui();
|
||||
};
|
||||
|
||||
field_amplify.set_value(1); // 1X is default (no amplification)
|
||||
field_amplify.on_change = [this](int32_t) {
|
||||
refresh_ui();
|
||||
};
|
||||
|
||||
button_trim.on_select = [this](Button&) {
|
||||
if (trim_capture()) {
|
||||
profile_capture();
|
||||
@@ -133,7 +139,18 @@ void IQTrimView::focus() {
|
||||
void IQTrimView::refresh_ui() {
|
||||
field_path.set_text(path_.filename().string());
|
||||
text_samples.set(to_string_dec_uint(info_->sample_count));
|
||||
text_max.set(to_string_dec_uint(info_->max_power));
|
||||
|
||||
// show max power after amplification applied
|
||||
uint64_t power_amp = field_amplify.value() * field_amplify.value() * field_amplify.value() * field_amplify.value();
|
||||
text_max.set(to_string_dec_uint(info_->max_power * power_amp));
|
||||
|
||||
// show max power in red if amplification is too high, causing clipping
|
||||
uint32_t clipping_limit = (fs::capture_file_sample_size(path_) == sizeof(complex8_t)) ? 0x80 : 0x8000;
|
||||
if ((field_amplify.value() * info_->max_iq) > clipping_limit)
|
||||
text_max.set_style(&Styles::red);
|
||||
else
|
||||
text_max.set_style(&Styles::light_grey);
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -191,7 +208,7 @@ bool IQTrimView::trim_capture() {
|
||||
}
|
||||
|
||||
progress_ui.show_trimming();
|
||||
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback());
|
||||
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback(), field_amplify.value());
|
||||
progress_ui.clear();
|
||||
|
||||
if (!trimmed)
|
||||
|
||||
@@ -107,6 +107,8 @@ class IQTrimView : public View {
|
||||
{{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()},
|
||||
{{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()},
|
||||
{{12 * 8, 10 * 16}, "%", Color::light_grey()},
|
||||
{{0 * 8, 12 * 16}, "Amplify:", Color::light_grey()},
|
||||
{{10 * 8, 12 * 16}, "x", Color::light_grey()},
|
||||
};
|
||||
|
||||
TextField field_path{
|
||||
@@ -135,7 +137,7 @@ class IQTrimView : public View {
|
||||
"0"};
|
||||
|
||||
Text text_max{
|
||||
{9 * 8, 9 * 16, 10 * 8, 1 * 16},
|
||||
{9 * 8, 9 * 16, 20 * 8, 1 * 16},
|
||||
"0"};
|
||||
|
||||
NumberField field_cutoff{
|
||||
@@ -145,6 +147,13 @@ class IQTrimView : public View {
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_amplify{
|
||||
{9 * 8, 12 * 16},
|
||||
1,
|
||||
{1, 9},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Button button_trim{
|
||||
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
|
||||
"Trim"};
|
||||
|
||||
@@ -101,6 +101,7 @@ class JammerView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
3500000 /* bandwidth */,
|
||||
3072000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -222,8 +222,6 @@ KeyfobView::KeyfobView(
|
||||
|
||||
options_make.set_selected_index(0);
|
||||
|
||||
transmitter_model.set_target_frequency(433920000); // Fixed 433.92MHz
|
||||
|
||||
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) {
|
||||
|
||||
@@ -44,6 +44,7 @@ class KeyfobView : public View {
|
||||
NavigationView& nav_;
|
||||
|
||||
TxRadioState radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
OOK_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -127,6 +127,8 @@ void LCRView::start_tx(const bool scan) {
|
||||
|
||||
modems::generate_data(lcr::generate_message(rgsb, litterals_list, options_ec.selected_index()), lcr_message_data);
|
||||
|
||||
/* It is AFSK modulation , measuring original fw 1.7.4 spectrum BW is just around 30khz , NBFM */
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF 1M75, same spectrum as previous fw 1.7.4
|
||||
transmitter_model.enable();
|
||||
|
||||
memcpy(shared_memory.bb_data.data, lcr_message_data, sizeof(lcr_message_data));
|
||||
|
||||
@@ -82,6 +82,7 @@ class LCRView : public View {
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
AFSK_TX_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -101,8 +101,11 @@ void MicTXView::set_tx(bool enable) {
|
||||
transmitting = false;
|
||||
configure_baseband();
|
||||
transmitter_model.disable();
|
||||
if (rx_enabled) // If audio RX is enabled and we've been transmitting
|
||||
rxaudio(true); // Turn back on audio RX
|
||||
if (rx_enabled) { // If audio RX is enabled and we've been transmitting
|
||||
rxaudio(true); // Turn back on audio RX
|
||||
vumeter.set_value(0); // Reset vumeter
|
||||
vumeter.dirty(); // Force to refresh vumeter.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +186,7 @@ void MicTXView::rxaudio(bool is_on) {
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
|
||||
audio::output::stop();
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
|
||||
portapack::pin_i2s0_rx_sda.mode(3);
|
||||
configure_baseband();
|
||||
}
|
||||
@@ -215,6 +218,7 @@ MicTXView::MicTXView(
|
||||
&field_frequency,
|
||||
&options_tone_key,
|
||||
&check_rogerbeep,
|
||||
&check_mic_to_HP, // check box to activate "hear mic"
|
||||
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
|
||||
&check_rxactive,
|
||||
&field_volume,
|
||||
@@ -242,6 +246,7 @@ MicTXView::MicTXView(
|
||||
&field_frequency,
|
||||
&options_tone_key,
|
||||
&check_rogerbeep,
|
||||
&check_mic_to_HP, // check box to activate "hear mic"
|
||||
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
|
||||
&check_rxactive,
|
||||
&field_volume,
|
||||
@@ -285,17 +290,17 @@ MicTXView::MicTXView(
|
||||
shift_bits_s16 = 6; // -08 dB’s respect ref level, (when +20dB's boost OFF)
|
||||
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
|
||||
}
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off
|
||||
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off,and the check box "Hear Mic"
|
||||
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
|
||||
};
|
||||
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's.
|
||||
} else {
|
||||
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod, shift audio data for AK4951, using top 8 bits s16 data (>>8)
|
||||
options_ak4951_alc_mode.on_change = [this](size_t, int8_t v) {
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options
|
||||
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
|
||||
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options, and the check box "Hear Mic"
|
||||
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
|
||||
};
|
||||
}
|
||||
|
||||
@@ -475,6 +480,20 @@ MicTXView::MicTXView(
|
||||
rogerbeep_enabled = v;
|
||||
};
|
||||
|
||||
check_mic_to_HP.on_select = [this](Checkbox&, bool v) {
|
||||
mic_to_HP_enabled = v;
|
||||
if (mic_to_HP_enabled)
|
||||
audio::input::loopback_mic_to_hp_enable();
|
||||
else
|
||||
audio::input::loopback_mic_to_hp_disable();
|
||||
if (mic_to_HP_enabled) { // When user click to "hear mic to HP", we will select the higher acoustic sound Option MODE ALC or BOOST-
|
||||
if (audio::debug::codec_name() == "WM8731") {
|
||||
options_wm8731_boost_mode.set_selected_index(0); // In WM we always go to Boost +12 dB’s respect reference level
|
||||
} else if (ak4951_alc_and_wm8731_boost_GUI == 0) // In AK we are changing only that ALC index =0, because in that option, there is no sound.
|
||||
options_ak4951_alc_mode.set_selected_index(1); // alc_mode =0 , means no ALC,no DIGITAL filter block (by passed), and that mode has no loopback mode.
|
||||
}
|
||||
};
|
||||
|
||||
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
|
||||
bool_same_F_tx_rx_enabled = v;
|
||||
field_rxfrequency.hidden(v); // Hide or show separated freq RX field. (When no hide user can enter down indep. freq for RX)
|
||||
@@ -501,8 +520,12 @@ MicTXView::MicTXView(
|
||||
check_rxactive.on_select = [this](Checkbox&, bool v) {
|
||||
// vumeter.set_value(0); //Start with a clean vumeter
|
||||
rx_enabled = v;
|
||||
check_mic_to_HP.hidden(rx_enabled); // Togle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
|
||||
if ((rx_enabled) && (transmitting))
|
||||
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
|
||||
|
||||
// check_va.hidden(v); //Hide or show voice activation
|
||||
rxaudio(v); // Activate-Deactivate audio rx accordingly
|
||||
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
|
||||
set_dirty(); // Refresh interface
|
||||
};
|
||||
|
||||
@@ -602,7 +625,7 @@ MicTXView::MicTXView(
|
||||
set_tx(false);
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
|
||||
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
|
||||
}
|
||||
|
||||
MicTXView::MicTXView(
|
||||
@@ -639,8 +662,10 @@ MicTXView::~MicTXView() {
|
||||
audio::input::stop();
|
||||
transmitter_model.set_target_frequency(tx_frequency);
|
||||
transmitter_model.disable();
|
||||
if (rx_enabled) // Also turn off audio rx if enabled
|
||||
if (rx_enabled) { // Also turn off both (audio rx if enabled, and disable mic_loop to HP)
|
||||
rxaudio(false);
|
||||
audio::input::loopback_mic_to_hp_disable(); // Leave Mic audio off in the main menu (as original audio path, otherwise we had No audio in next "Audio App")
|
||||
}
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ class MicTXView : public View {
|
||||
|
||||
RxRadioState rx_radio_state_{};
|
||||
TxRadioState tx_radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
sampling_rate /* sampling rate */
|
||||
};
|
||||
@@ -93,6 +94,7 @@ class MicTXView : public View {
|
||||
bool va_enabled{false};
|
||||
bool ptt_enabled{true};
|
||||
bool rogerbeep_enabled{false};
|
||||
bool mic_to_HP_enabled{false};
|
||||
bool bool_same_F_tx_rx_enabled{false};
|
||||
bool rx_enabled{false};
|
||||
uint32_t tone_key_index{};
|
||||
@@ -276,6 +278,12 @@ class MicTXView : public View {
|
||||
"Roger beep",
|
||||
false};
|
||||
|
||||
Checkbox check_mic_to_HP{
|
||||
{18 * 8, (14 * 8) + 4},
|
||||
10,
|
||||
"Hear Mic",
|
||||
false};
|
||||
|
||||
Checkbox check_rxactive{
|
||||
{3 * 8, (21 * 8) - 7},
|
||||
8, // it was 18, but if it is string size should be 8
|
||||
|
||||
@@ -117,6 +117,8 @@ bool MorseView::start_tx() {
|
||||
}
|
||||
|
||||
progressbar.set_max(symbol_count);
|
||||
// By experimental spectrum measurment, we left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min).
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF .already tested in FM morse max tone 9,999k , max dev 150khz
|
||||
transmitter_model.enable();
|
||||
|
||||
if (modulation == CW) {
|
||||
|
||||
@@ -70,6 +70,7 @@ class MorseView : public View {
|
||||
uint32_t time_units{0};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -54,7 +54,6 @@ NRFRxView::NRFRxView(NavigationView& nav)
|
||||
&console});
|
||||
|
||||
// Auto-configure modem for LCR RX (will be removed later)
|
||||
field_frequency.set_value(2480000000);
|
||||
auto def_bell202 = &modem_defs[0];
|
||||
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
|
||||
serial_format_t serial_format;
|
||||
|
||||
@@ -50,6 +50,7 @@ class NRFRxView : public View {
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
2480000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
|
||||
@@ -56,6 +56,7 @@ class NumbersStationView : public View {
|
||||
NavigationView& nav_;
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ class NuoptixView : public View {
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -81,6 +81,8 @@ bool POCSAGTXView::start_tx() {
|
||||
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_tx_gain(40);
|
||||
// We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF . Pocsag is NBFM , using BW channel 25khz or 12khz
|
||||
transmitter_model.enable();
|
||||
|
||||
uint8_t* data_ptr = shared_memory.bb_data.data;
|
||||
|
||||
@@ -60,6 +60,7 @@ class POCSAGTXView : public View {
|
||||
NavigationView& nav_;
|
||||
|
||||
TxRadioState radio_state_{
|
||||
466175000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -187,7 +187,6 @@ void RDSView::start_tx() {
|
||||
else
|
||||
frame_datetime.clear();
|
||||
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Big Spectrum harmonics reduction, and now quicker decoding time.
|
||||
transmitter_model.enable();
|
||||
|
||||
tx_thread = std::make_unique<RDSThread>(frames);
|
||||
|
||||
@@ -142,6 +142,7 @@ class RDSView : public View {
|
||||
RDS_flags rds_flags{};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -494,6 +494,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
|
||||
button_manual_recon.on_select = [this](Button&) {
|
||||
button_remove.set_text("DELETE");
|
||||
button_add.hidden(false);
|
||||
scanner_mode = false;
|
||||
manual_mode = true;
|
||||
recon_pause();
|
||||
@@ -620,7 +621,8 @@ ReconView::ReconView(NavigationView& nav)
|
||||
recon_pause();
|
||||
}
|
||||
button_add.hidden(scanner_mode);
|
||||
set_dirty();
|
||||
if (scanner_mode) // only needed when hiding, UI mayhem
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
button_config.on_select = [this, &nav](Button&) {
|
||||
@@ -775,19 +777,8 @@ void ReconView::frequency_file_load(bool) {
|
||||
}
|
||||
|
||||
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
systime_t time_interval = 100;
|
||||
uint32_t local_recon_lock_duration = recon_lock_duration;
|
||||
|
||||
chrono_end = chTimeNow();
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION) {
|
||||
time_interval = chrono_end - chrono_start;
|
||||
if (field_lock_wait.value() == 0) {
|
||||
if (time_interval <= 1) // capping here to avoid freeze because too quick
|
||||
local_recon_lock_duration = 2; // minimum working tested value
|
||||
else
|
||||
local_recon_lock_duration = time_interval;
|
||||
}
|
||||
}
|
||||
systime_t time_interval = chrono_end - chrono_start;
|
||||
chrono_start = chrono_end;
|
||||
|
||||
// hack to reload the list if it was cleared by going into CONFIG
|
||||
@@ -807,7 +798,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (!timer) {
|
||||
status = 0;
|
||||
freq_lock = 0;
|
||||
timer = local_recon_lock_duration;
|
||||
timer = recon_lock_duration;
|
||||
}
|
||||
if (freq_lock < recon_lock_nb_match) // LOCKING
|
||||
{
|
||||
|
||||
@@ -303,7 +303,7 @@ class ReconView : public View {
|
||||
NumberField field_lock_wait{
|
||||
{25 * 8, 24 * 8 + 4},
|
||||
4,
|
||||
{0, RECON_MAX_LOCK_DURATION},
|
||||
{STATS_UPDATE_INTERVAL, RECON_MAX_LOCK_DURATION},
|
||||
STATS_UPDATE_INTERVAL,
|
||||
' ',
|
||||
};
|
||||
|
||||
@@ -90,6 +90,7 @@ class SearchView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
100'000'000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
SEARCH_SLICE_WIDTH /* sampling rate */,
|
||||
ReceiverModel::Mode::SpectrumAnalysis};
|
||||
|
||||
@@ -343,34 +343,6 @@ void SetUIView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetAppSettingsView ************************************/
|
||||
|
||||
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&checkbox_load_app_settings,
|
||||
&checkbox_save_app_settings,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
checkbox_load_app_settings.set_value(pmem::load_app_settings());
|
||||
checkbox_save_app_settings.set_value(pmem::save_app_settings());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_load_app_settings(checkbox_load_app_settings.value());
|
||||
pmem::set_save_app_settings(checkbox_save_app_settings.value());
|
||||
nav.pop();
|
||||
};
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetAppSettingsView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetConverterSettingsView ******************************/
|
||||
|
||||
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
|
||||
@@ -654,7 +626,6 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetAppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
|
||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
|
||||
|
||||
@@ -310,41 +310,6 @@ class SetUIView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
class SetAppSettingsView : public View {
|
||||
public:
|
||||
SetAppSettingsView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "App Settings"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "App settings are saved to", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "the SD card in /SETTINGS.", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "Radio settings may also be", Color::light_grey()},
|
||||
{{1 * 8, 4 * 16}, "loaded or saved per app.", Color::light_grey()},
|
||||
};
|
||||
|
||||
Checkbox checkbox_load_app_settings{
|
||||
{3 * 8, 6 * 16},
|
||||
25,
|
||||
"Load radio settings"};
|
||||
|
||||
Checkbox checkbox_save_app_settings{
|
||||
{3 * 8, 8 * 16},
|
||||
25,
|
||||
"Save radio settings"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SetConverterSettingsView : public View {
|
||||
public:
|
||||
SetConverterSettingsView(NavigationView& nav);
|
||||
|
||||
@@ -51,6 +51,7 @@ class SigGenView : public View {
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
@@ -88,7 +89,7 @@ class SigGenView : public View {
|
||||
{&bitmap_sig_noise, 6}}};
|
||||
|
||||
Text text_shape{
|
||||
{15 * 8, 4 + 10, 8 * 8, 16},
|
||||
{15 * 8, 4 + 10, 15 * 8, 16},
|
||||
""};
|
||||
|
||||
SymField symfield_tone{
|
||||
|
||||
@@ -66,9 +66,6 @@ SondeView::SondeView(NavigationView& nav)
|
||||
&button_see_qr,
|
||||
&button_see_map});
|
||||
|
||||
if (!settings_.radio_loaded())
|
||||
field_frequency.set_value(initial_target_frequency);
|
||||
|
||||
field_frequency.set_step(500); // euquiq: was 10000, but we are using this for fine-tunning
|
||||
|
||||
geopos.set_read_only(true);
|
||||
|
||||
@@ -68,6 +68,7 @@ class SondeView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
402700000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */
|
||||
};
|
||||
@@ -75,7 +76,6 @@ class SondeView : public View {
|
||||
"rx_sonde", app_settings::Mode::RX};
|
||||
|
||||
std::unique_ptr<SondeLogger> logger{};
|
||||
uint32_t target_frequency_{402700000};
|
||||
bool logging{false};
|
||||
bool use_crc{false};
|
||||
bool beep{false};
|
||||
|
||||
@@ -85,7 +85,11 @@ SpectrumPainterView::SpectrumPainterView(
|
||||
|
||||
if (tx_mode == 0 && image_input_avaliable == false)
|
||||
return;
|
||||
|
||||
/* By experimental test measurement, we got a good painted spectrum quality when selecting
|
||||
a BW GUI App range from 100k ... 2M aprox. In that range , the best TX LPF filter = 1M75 (the min)
|
||||
With this min TX LPF filter , we minimized the harmonics aliasing multipicture overlapped.
|
||||
So original fw 1.7.4., was already selecting the best setting (1M75) */
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Already tested, is the best setting for this App.
|
||||
transmitter_model.enable();
|
||||
|
||||
if (persistent_memory::stealth_mode()) {
|
||||
|
||||
@@ -160,6 +160,7 @@ void SSTVTXView::start_tx() {
|
||||
scanline_counter = 0;
|
||||
prepare_scanline(); // Preload one scanline
|
||||
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF 1M75, same spectrum as previous fw 1.7.4
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_sstv_data(
|
||||
|
||||
@@ -62,6 +62,7 @@ class TestView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2457600 * 2 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -85,7 +85,6 @@ void TouchTunesView::on_tx_progress(const uint32_t progress, const bool done) {
|
||||
// transmission events.
|
||||
void TouchTunesView::start_ew() {
|
||||
// Radio
|
||||
transmitter_model.set_target_frequency(433920000);
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_tx_gain(47);
|
||||
transmitter_model.enable();
|
||||
@@ -151,10 +150,7 @@ void TouchTunesView::start_tx(const uint32_t button_index) {
|
||||
|
||||
size_t bitstream_length = make_bitstream(fragments);
|
||||
|
||||
transmitter_model.set_target_frequency(433920000);
|
||||
transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_baseband_bandwidth(1750000);
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_ook_data(
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "encoders.hpp"
|
||||
|
||||
// The coding in notpike's script is quite complex, using multiple LUTs to form the data sent to the YSO.
|
||||
// The format is actually very simple if it is rather seen as short and long gaps between pulses (as seen in many OOK remotes).
|
||||
@@ -115,8 +116,9 @@ class TouchTunesView : public View {
|
||||
|
||||
private:
|
||||
TxRadioState radio_state_{
|
||||
3500000 /* bandwidth */,
|
||||
3072000 /* sampling rate */
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
OOK_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
uint32_t scan_button_index{};
|
||||
|
||||
@@ -198,8 +198,8 @@ void update_audio_mute() {
|
||||
|
||||
namespace input {
|
||||
|
||||
void start(int8_t alc_mode) {
|
||||
audio_codec->microphone_enable(alc_mode); // added user-GUI selection for AK4951, ALC mode parameter.
|
||||
void start(int8_t alc_mode, bool mic_to_HP_enabled) {
|
||||
audio_codec->microphone_enable(alc_mode, mic_to_HP_enabled); // added user-GUI selection for AK4951, ALC mode parameter. and the check box "Hear Mic"
|
||||
i2s::i2s0::rx_start();
|
||||
}
|
||||
|
||||
@@ -208,6 +208,14 @@ void stop() {
|
||||
audio_codec->microphone_disable();
|
||||
}
|
||||
|
||||
void loopback_mic_to_hp_enable() {
|
||||
audio_codec->microphone_to_HP_enable();
|
||||
}
|
||||
|
||||
void loopback_mic_to_hp_disable() {
|
||||
audio_codec->microphone_to_HP_disable();
|
||||
}
|
||||
|
||||
} /* namespace input */
|
||||
|
||||
namespace headphone {
|
||||
|
||||
@@ -50,9 +50,12 @@ class Codec {
|
||||
virtual volume_range_t headphone_gain_range() const = 0;
|
||||
virtual void set_headphone_volume(const volume_t volume) = 0;
|
||||
|
||||
virtual void microphone_enable(int8_t alc_mode) = 0; // added user-GUI AK4951 ,selected ALC mode.
|
||||
virtual void microphone_enable(int8_t alc_mode, bool mic_to_HP_enabled) = 0; // added user-GUI AK4951 ,selected ALC mode.
|
||||
virtual void microphone_disable() = 0;
|
||||
|
||||
virtual void microphone_to_HP_enable() = 0;
|
||||
virtual void microphone_to_HP_disable() = 0;
|
||||
|
||||
virtual size_t reg_count() const = 0;
|
||||
virtual size_t reg_bits() const = 0;
|
||||
virtual uint32_t reg_read(const size_t register_number) = 0;
|
||||
@@ -76,9 +79,12 @@ void update_audio_mute();
|
||||
|
||||
namespace input {
|
||||
|
||||
void start(int8_t alc_mode); // added parameter user-GUI select AK4951-ALC mode for config mic path,(recording mode in datasheet),
|
||||
void start(int8_t alc_mode, bool mic_to_HP_enabled); // added parameters -GUI select AK4951-ALC mode for config mic path,(recording mode),and the check box "Hear Mic"
|
||||
void stop();
|
||||
|
||||
void loopback_mic_to_hp_enable();
|
||||
void loopback_mic_to_hp_disable();
|
||||
|
||||
} /* namespace input */
|
||||
|
||||
namespace headphone {
|
||||
|
||||
@@ -149,12 +149,9 @@ void set_aprs(const uint32_t baudrate) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) {
|
||||
void set_btle(uint8_t channel_number) {
|
||||
const BTLERxConfigureMessage message{
|
||||
baudrate,
|
||||
word_length,
|
||||
trigger_value,
|
||||
trigger_word};
|
||||
channel_number};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
@@ -167,6 +164,17 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_fsk(const size_t deviation) {
|
||||
const FSKRxConfigureMessage message{
|
||||
taps_200k_decim_0,
|
||||
taps_16k0_decim_1,
|
||||
taps_11k0_channel,
|
||||
2,
|
||||
deviation};
|
||||
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count) {
|
||||
const AFSKTxConfigureMessage message{
|
||||
afsk_samples_per_bit,
|
||||
|
||||
@@ -69,9 +69,10 @@ void set_pitch_rssi(int32_t avg, bool enabled);
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
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_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
|
||||
void set_btle(uint8_t channel_number);
|
||||
|
||||
void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ uint32_t fault_address{0};
|
||||
void draw_guru_meditation_header(uint8_t source, const char* hint) {
|
||||
Painter painter;
|
||||
|
||||
// disable scroll in case a waterfall/spectrum display was active (scroll would mess up the fault display)
|
||||
portapack::display.scroll_disable();
|
||||
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
Color::red());
|
||||
@@ -134,6 +137,10 @@ void runtime_error(uint8_t source) {
|
||||
|
||||
led.off();
|
||||
|
||||
// wait for DFU button release if pressed, so we don't immediately jump into stack dump
|
||||
while (swizzled_switches() & (1 << (int)Switch::Dfu))
|
||||
;
|
||||
|
||||
while (true) {
|
||||
volatile size_t n = 1000000U;
|
||||
while (n--)
|
||||
@@ -243,10 +250,6 @@ void draw_stack_dump() {
|
||||
p = &__process_stack_base__;
|
||||
break;
|
||||
}
|
||||
|
||||
// If SEL button pressed, try writing to file (likely to hang in fault condition but heh)
|
||||
if (switches & (1 << (int)Switch::Sel))
|
||||
stack_dump();
|
||||
}
|
||||
|
||||
// clear screen to allow new range to be displayed, if we're not at the end
|
||||
|
||||
@@ -11,10 +11,15 @@ set(EXTCPPSRC
|
||||
#calculator
|
||||
external/calculator/main.cpp
|
||||
external/calculator/ui_calculator.cpp
|
||||
|
||||
#font_viewer
|
||||
external/font_viewer/main.cpp
|
||||
external/font_viewer/ui_font_viewer.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
pacman
|
||||
afsk_rx
|
||||
calculator
|
||||
font_viewer
|
||||
)
|
||||
|
||||
+7
@@ -20,6 +20,7 @@ MEMORY
|
||||
ram_external_app_pacman (rwx) : org = 0xEEE90000, len = 32k
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xEEEA0000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xEEEB0000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xEEEC0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -41,4 +42,10 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_calculator.application_information));
|
||||
*(*ui*external_app*calculator*);
|
||||
} > ram_external_app_calculator
|
||||
|
||||
.external_app_font_viewer : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_font_viewer.application_information));
|
||||
*(*ui*external_app*font_viewer*);
|
||||
} > ram_external_app_font_viewer
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* 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_font_viewer.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::font_viewer {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<DebugFontsView>();
|
||||
}
|
||||
} // namespace ui::external_app::font_viewer
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_font_viewer.application_information"), used)) application_information_t _application_information_font_viewer = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::font_viewer::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Font Viewer",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x10,
|
||||
0x0C,
|
||||
0x38,
|
||||
0x0C,
|
||||
0x38,
|
||||
0x0C,
|
||||
0x6C,
|
||||
0x0C,
|
||||
0x6C,
|
||||
0x0C,
|
||||
0xC6,
|
||||
0x7C,
|
||||
0xFE,
|
||||
0xFC,
|
||||
0xFF,
|
||||
0x8D,
|
||||
0x83,
|
||||
0x8D,
|
||||
0x83,
|
||||
0xFD,
|
||||
0x01,
|
||||
0x7D,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::DEBUG,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_noop */ {'\0', '\0', '\0', '\0'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* 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_font_viewer.hpp"
|
||||
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::font_viewer {
|
||||
|
||||
/* DebugFontsView *******************************************************/
|
||||
|
||||
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
|
||||
auto char_width{font_style->font.char_width()};
|
||||
auto char_height{font_style->font.line_height()};
|
||||
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
|
||||
uint16_t line_pos{y_offset};
|
||||
|
||||
painter.draw_string({0, y_offset}, *font_style, font_name);
|
||||
|
||||
// Displaying ASCII+extended characters from 0x20 to 0xFF
|
||||
for (uint8_t c = 0; c <= 0xDF; c++) {
|
||||
line_pos = y_offset + ((c / cpl) + 2) * char_height;
|
||||
|
||||
if ((c % cpl) == 0)
|
||||
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
|
||||
|
||||
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
|
||||
}
|
||||
|
||||
return line_pos + char_height;
|
||||
}
|
||||
|
||||
void DebugFontsView::paint(Painter& painter) {
|
||||
int16_t line_pos;
|
||||
|
||||
line_pos = display_font(painter, 32, &Styles::white, "Fixed 8x16");
|
||||
display_font(painter, line_pos + 16, &Styles::white_small, "Fixed 5x8");
|
||||
}
|
||||
|
||||
DebugFontsView::DebugFontsView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
} /* namespace ui::external_app::font_viewer */
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
*
|
||||
* 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_DEBUG_H__
|
||||
#define __UI_DEBUG_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::font_viewer {
|
||||
|
||||
class DebugFontsView : public View {
|
||||
public:
|
||||
DebugFontsView(NavigationView& nav);
|
||||
void paint(Painter& painter) override;
|
||||
std::string title() const override { return "Fonts"; };
|
||||
|
||||
private:
|
||||
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
|
||||
NavigationView& nav_;
|
||||
};
|
||||
|
||||
} /* namespace ui::external_app::font_viewer */
|
||||
|
||||
#endif /*__UI_FONT_VIEWER_H__*/
|
||||
@@ -325,6 +325,14 @@ FATTimestamp file_created_date(const std::filesystem::path& file_path) {
|
||||
return {filinfo.fdate, filinfo.ftime};
|
||||
}
|
||||
|
||||
std::filesystem::filesystem_error file_update_date(const std::filesystem::path& file_path, FATTimestamp timestamp) {
|
||||
FILINFO filinfo{};
|
||||
|
||||
filinfo.fdate = timestamp.FAT_date;
|
||||
filinfo.ftime = timestamp.FAT_time;
|
||||
return f_utime(reinterpret_cast<const TCHAR*>(file_path.c_str()), &filinfo);
|
||||
}
|
||||
|
||||
std::filesystem::filesystem_error make_new_file(
|
||||
const std::filesystem::path& file_path) {
|
||||
File f;
|
||||
|
||||
@@ -270,6 +270,7 @@ std::filesystem::filesystem_error rename_file(const std::filesystem::path& file_
|
||||
std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_path, const std::filesystem::path& dest_path);
|
||||
|
||||
FATTimestamp file_created_date(const std::filesystem::path& file_path);
|
||||
std::filesystem::filesystem_error file_update_date(const std::filesystem::path& file_path, FATTimestamp timestamp);
|
||||
std::filesystem::filesystem_error make_new_file(const std::filesystem::path& file_path);
|
||||
std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path);
|
||||
std::filesystem::filesystem_error ensure_directory(const std::filesystem::path& dir_path);
|
||||
|
||||
@@ -36,6 +36,13 @@ uint32_t power(T value) {
|
||||
return (real * real) + (imag * imag);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
uint32_t iq_max(T value) {
|
||||
auto real = abs(value.real());
|
||||
auto imag = abs(value.imag());
|
||||
return (real > imag) ? real : imag;
|
||||
}
|
||||
|
||||
/* Collects capture file metadata and sample power buckets. */
|
||||
template <typename T>
|
||||
Optional<CaptureInfo> profile_capture(
|
||||
@@ -51,7 +58,8 @@ Optional<CaptureInfo> profile_capture(
|
||||
.file_size = f.size(),
|
||||
.sample_count = f.size() / sizeof(T),
|
||||
.sample_size = sizeof(T),
|
||||
.max_power = 0};
|
||||
.max_power = 0,
|
||||
.max_iq = 0};
|
||||
|
||||
auto profile_samples = buckets.size * samples_per_bucket;
|
||||
auto sample_interval = info.sample_count / profile_samples;
|
||||
@@ -67,8 +75,11 @@ Optional<CaptureInfo> profile_capture(
|
||||
if (*result != info.sample_size)
|
||||
break; // EOF
|
||||
|
||||
auto mag_squared = power(value);
|
||||
auto max_iq = iq_max(value);
|
||||
if (max_iq > info.max_iq)
|
||||
info.max_iq = max_iq;
|
||||
|
||||
auto mag_squared = power(value);
|
||||
if (mag_squared > info.max_power)
|
||||
info.max_power = mag_squared;
|
||||
|
||||
@@ -133,13 +144,50 @@ TrimRange compute_trim_range(
|
||||
info.sample_size};
|
||||
}
|
||||
|
||||
void amplify_iq_buffer(uint8_t* buffer, uint32_t length, uint32_t amplification, uint8_t sample_size) {
|
||||
uint32_t mult_count = length / sample_size / 2;
|
||||
|
||||
switch (sample_size) {
|
||||
case sizeof(complex16_t): {
|
||||
int16_t* buf_ptr = (int16_t*)buffer;
|
||||
for (uint32_t i = 0; i < mult_count; i++) {
|
||||
int32_t val = *buf_ptr * amplification;
|
||||
if (val > 0x7FFF)
|
||||
val = 0x7FFF;
|
||||
else if (val < -0x7FFF)
|
||||
val = -0x7FFF;
|
||||
*buf_ptr++ = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case sizeof(complex8_t): {
|
||||
int8_t* buf_ptr = (int8_t*)buffer;
|
||||
for (uint32_t i = 0; i < mult_count; i++) {
|
||||
int32_t val = *buf_ptr * amplification;
|
||||
if (val > 0x7F)
|
||||
val = 0x7F;
|
||||
else if (val < -0x7F)
|
||||
val = -0x7F;
|
||||
*buf_ptr++ = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool trim_capture_with_range(
|
||||
const fs::path& path,
|
||||
TrimRange range,
|
||||
const std::function<void(uint8_t)>& on_progress) {
|
||||
const std::function<void(uint8_t)>& on_progress,
|
||||
const uint32_t amplification) {
|
||||
constexpr size_t buffer_size = std::filesystem::max_file_block_size;
|
||||
uint8_t buffer[buffer_size];
|
||||
auto temp_path = path + u"-tmp";
|
||||
auto sample_size = fs::capture_file_sample_size(path);
|
||||
|
||||
// end_sample is the first sample to _not_ include.
|
||||
auto start_byte = range.start_sample * range.sample_size;
|
||||
@@ -168,6 +216,9 @@ bool trim_capture_with_range(
|
||||
auto remaining = length - processed;
|
||||
auto to_write = std::min(remaining, *result);
|
||||
|
||||
if (amplification > 1)
|
||||
amplify_iq_buffer(buffer, to_write, amplification, sample_size);
|
||||
|
||||
result = dst->write(buffer, to_write);
|
||||
if (result.is_error()) return false;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ struct CaptureInfo {
|
||||
uint64_t sample_count;
|
||||
uint8_t sample_size;
|
||||
uint32_t max_power;
|
||||
uint32_t max_iq;
|
||||
};
|
||||
|
||||
/* Holds sample average power by bucket. */
|
||||
@@ -82,11 +83,19 @@ TrimRange compute_trim_range(
|
||||
const PowerBuckets& buckets,
|
||||
uint8_t cutoff_percent);
|
||||
|
||||
/* Multiplies samples in an IQ buffer by amplification value */
|
||||
void amplify_iq_buffer(
|
||||
uint8_t* buffer,
|
||||
uint32_t length,
|
||||
uint32_t amplification,
|
||||
uint8_t sample_size);
|
||||
|
||||
/* Trims the capture file with the specified range. */
|
||||
bool trim_capture_with_range(
|
||||
const std::filesystem::path& path,
|
||||
TrimRange range,
|
||||
const std::function<void(uint8_t)>& on_progress);
|
||||
const std::function<void(uint8_t)>& on_progress,
|
||||
const uint32_t amplification);
|
||||
|
||||
} // namespace iq
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ max2839::MAX2839 second_if_max2839{ssp1_target_max283x};
|
||||
static max5864::MAX5864 baseband_codec{ssp1_target_max5864};
|
||||
static baseband::CPLD baseband_cpld;
|
||||
|
||||
// Set invalid to force the set_direction CPLD workaround to run.
|
||||
static rf::Direction direction{-1};
|
||||
// load_sram() is called at boot in portapack.cpp, including verify CPLD part, so default direction is Receive
|
||||
static rf::Direction direction{rf::Direction::Receive};
|
||||
static bool baseband_invert = false;
|
||||
static bool mixer_invert = false;
|
||||
|
||||
|
||||
@@ -41,8 +41,10 @@ class RadioState {
|
||||
model->initialize();
|
||||
}
|
||||
|
||||
RadioState(uint32_t new_bandwidth, uint32_t new_sampling_rate) {
|
||||
RadioState(uint32_t new_frequency, uint32_t new_bandwidth, uint32_t new_sampling_rate) {
|
||||
model->initialize();
|
||||
if (new_frequency != 0)
|
||||
model->set_target_frequency(new_frequency);
|
||||
model->set_sampling_rate(new_sampling_rate);
|
||||
model->set_baseband_bandwidth(new_bandwidth);
|
||||
}
|
||||
@@ -61,10 +63,13 @@ class RadioState {
|
||||
typename U = TModel,
|
||||
typename Mode = std::enable_if_t<sizeof(typename U::Mode), typename U::Mode> >
|
||||
RadioState(
|
||||
uint32_t new_frequency,
|
||||
uint32_t new_bandwidth,
|
||||
uint32_t new_sampling_rate,
|
||||
Mode new_mode) {
|
||||
model->initialize();
|
||||
if (new_frequency != 0)
|
||||
model->set_target_frequency(new_frequency);
|
||||
model->set_sampling_rate(new_sampling_rate);
|
||||
model->set_baseband_bandwidth(new_bandwidth);
|
||||
model->settings().mode = new_mode;
|
||||
|
||||
@@ -88,6 +88,13 @@ static std::pair<typename ContainerType::const_iterator, typename ContainerType:
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
template <typename ContainerType, typename KeySelector, typename SortOrder>
|
||||
void sortEntriesBy(ContainerType& entries, KeySelector keySelector, SortOrder ascending) {
|
||||
entries.sort([keySelector, ascending](const auto& a, const auto& b) {
|
||||
return ascending ? keySelector(a) < keySelector(b) : keySelector(a) > keySelector(b);
|
||||
});
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
using RecentEntriesColumn = std::pair<std::string, size_t>;
|
||||
|
||||
@@ -311,6 +311,18 @@ std::string to_string_file_size(uint32_t file_size) {
|
||||
return to_string_dec_uint(file_size) + suffix[suffix_index];
|
||||
}
|
||||
|
||||
std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length) {
|
||||
std::string string;
|
||||
|
||||
string += to_string_hex(macAddress[0], 2);
|
||||
|
||||
for (int i = 1; i < length; i++) {
|
||||
string += ":" + to_string_hex(macAddress[i], 2);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precision) {
|
||||
const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000};
|
||||
std::string string{""};
|
||||
|
||||
@@ -76,6 +76,9 @@ std::string to_string_FAT_timestamp(const FATTimestamp& timestamp);
|
||||
// Gets a human readable file size string.
|
||||
std::string to_string_file_size(uint32_t file_size);
|
||||
|
||||
// Converts Mac Address to string.
|
||||
std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length);
|
||||
|
||||
/* Scales 'n' to be a value less than 1000. 'base_unit' is the index of the unit from
|
||||
* 'unit_prefix' that 'n' is in initially. 3 is the index of the '1s' unit. */
|
||||
std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precision);
|
||||
|
||||
@@ -110,16 +110,18 @@ void BtnGridView::clear() {
|
||||
|
||||
void BtnGridView::add_items(std::initializer_list<GridItem> new_items) {
|
||||
for (auto item : new_items) {
|
||||
menu_items.push_back(item);
|
||||
if (!blacklisted_app(item))
|
||||
menu_items.push_back(item);
|
||||
}
|
||||
|
||||
update_items();
|
||||
}
|
||||
|
||||
void BtnGridView::add_item(GridItem new_item) {
|
||||
menu_items.push_back(new_item);
|
||||
|
||||
update_items();
|
||||
if (!blacklisted_app(new_item)) {
|
||||
menu_items.push_back(new_item);
|
||||
update_items();
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::update_items() {
|
||||
@@ -232,4 +234,41 @@ bool BtnGridView::on_encoder(const EncoderEvent event) {
|
||||
return set_highlighted(highlighted_item + event);
|
||||
}
|
||||
|
||||
/* BlackList ******************************************************/
|
||||
|
||||
std::unique_ptr<char> blacklist_ptr{};
|
||||
size_t blacklist_len{};
|
||||
|
||||
void load_blacklist() {
|
||||
File f;
|
||||
|
||||
auto error = f.open(BLACKLIST);
|
||||
if (error)
|
||||
return;
|
||||
|
||||
// allocating two extra bytes for leading & trailing commas
|
||||
blacklist_ptr = std::unique_ptr<char>(new char[f.size() + 2]);
|
||||
if (f.read(blacklist_ptr.get() + 1, f.size())) {
|
||||
blacklist_len = f.size() + 2;
|
||||
|
||||
// replace any CR/LF characters with comma delineator, and add comma prefix/suffix, to simplify searching
|
||||
char* ptr = blacklist_ptr.get();
|
||||
*ptr = ',';
|
||||
*(ptr + blacklist_len - 1) = ',';
|
||||
for (size_t i = 0; i < blacklist_len; i++, ptr++) {
|
||||
if (*ptr == 0x0D || *ptr == 0x0A)
|
||||
*ptr = ',';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BtnGridView::blacklisted_app(GridItem new_item) {
|
||||
std::string app_name = "," + new_item.text + ",";
|
||||
|
||||
if (blacklist_len < app_name.size())
|
||||
return false;
|
||||
|
||||
return std::search(blacklist_ptr.get(), blacklist_ptr.get() + blacklist_len, app_name.begin(), app_name.end()) < blacklist_ptr.get() + blacklist_len;
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// file used for listing apps to hide from menu
|
||||
#define BLACKLIST u"/SETTINGS/blacklist"
|
||||
|
||||
namespace ui {
|
||||
|
||||
struct GridItem {
|
||||
@@ -47,6 +50,8 @@ struct GridItem {
|
||||
// TODO: Prevent default-constructed GridItems.
|
||||
};
|
||||
|
||||
void load_blacklist();
|
||||
|
||||
class BtnGridView : public View {
|
||||
public:
|
||||
BtnGridView(Rect new_parent_rect = {0, 0, 240, 304}, bool keep_highlight = false);
|
||||
@@ -70,6 +75,7 @@ class BtnGridView : public View {
|
||||
void on_blur() override;
|
||||
bool on_key(const KeyEvent event) override;
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool blacklisted_app(GridItem new_item);
|
||||
|
||||
private:
|
||||
int rows_{3};
|
||||
|
||||
@@ -149,7 +149,7 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
|
||||
map_zoom++;
|
||||
|
||||
// Ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
|
||||
if (240 % map_zoom != 0) {
|
||||
if (geomap_rect_width % map_zoom != 0) {
|
||||
map_zoom--;
|
||||
}
|
||||
} else if ((delta < 0) && (map_zoom > 1)) {
|
||||
@@ -171,7 +171,7 @@ void GeoMap::map_zoom_line(ui::Color* buffer) {
|
||||
|
||||
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
|
||||
// For 240 width, than means no check is needed for map_zoom values up to 6
|
||||
for (int i = (240 / map_zoom) - 1; i >= 0; i--) {
|
||||
for (int i = (geomap_rect_width / map_zoom) - 1; i >= 0; i--) {
|
||||
for (int j = 0; j < map_zoom; j++) {
|
||||
buffer[(i * map_zoom) + j] = buffer[i];
|
||||
}
|
||||
@@ -271,24 +271,22 @@ void GeoMap::move(const float lon, const float lat) {
|
||||
lon_ = lon;
|
||||
lat_ = lat;
|
||||
|
||||
Rect map_rect = screen_rect();
|
||||
|
||||
// Using WGS 84/Pseudo-Mercator projection
|
||||
x_pos = map_width * (lon_ + 180) / 360 - (map_rect.width() / 2);
|
||||
x_pos = map_width * (lon_ + 180) / 360 - (geomap_rect_width / 2);
|
||||
|
||||
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
|
||||
double lat_rad = sin(lat * pi / 180);
|
||||
y_pos = map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset) - 128; // Offset added for the GUI
|
||||
|
||||
// Cap position
|
||||
if (x_pos > (map_width - map_rect.width()))
|
||||
x_pos = map_width - map_rect.width();
|
||||
if (y_pos > (map_height + map_rect.height()))
|
||||
y_pos = map_height - map_rect.height();
|
||||
if (x_pos > (map_width - geomap_rect_width))
|
||||
x_pos = map_width - geomap_rect_width;
|
||||
if (y_pos > (map_height + geomap_rect_height))
|
||||
y_pos = map_height - geomap_rect_height;
|
||||
|
||||
// Scale calculation
|
||||
float km_per_deg_lon = cos(lat * pi / 180) * 111.321; // 111.321 km/deg longitude at equator, and 0 km at poles
|
||||
pixels_per_km = (map_rect.width() / 2) / km_per_deg_lon;
|
||||
pixels_per_km = (geomap_rect_width / 2) / km_per_deg_lon;
|
||||
}
|
||||
|
||||
bool GeoMap::init() {
|
||||
@@ -391,11 +389,10 @@ MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
|
||||
|
||||
// Check if it could be on screen
|
||||
// Only checking one direction to reduce CPU
|
||||
const auto r = screen_rect();
|
||||
double lat_rad = sin(marker.lat * pi / 180);
|
||||
int x = (map_width * (marker.lon + 180) / 360) - x_pos;
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
if (false == ((x >= 0) && (x < r.width()) && (y > 10) && (y < r.height()))) // Dont draw within symbol size of top
|
||||
if (false == ((x >= 0) && (x < geomap_rect_width) && (y > 10) && (y < geomap_rect_height))) // Dont draw within symbol size of top
|
||||
{
|
||||
ret = MARKER_NOT_STORED;
|
||||
} else if (markerListLen < NumMarkerListElements) {
|
||||
|
||||
@@ -178,6 +178,10 @@ class GeoMap : public Widget {
|
||||
void clear_markers();
|
||||
MapMarkerStored store_marker(GeoMarker& marker);
|
||||
|
||||
static const Dim banner_height = 3 * 16;
|
||||
static const Dim geomap_rect_width = 240;
|
||||
static const Dim geomap_rect_height = 320 - 16 - banner_height;
|
||||
|
||||
private:
|
||||
void draw_scale(Painter& painter);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
@@ -250,8 +254,6 @@ class GeoMapView : public View {
|
||||
void setup();
|
||||
|
||||
const std::function<void(int32_t, float, float)> on_done{};
|
||||
|
||||
const Dim banner_height = 3 * 16;
|
||||
GeoMapMode mode_{};
|
||||
int32_t altitude_{};
|
||||
GeoPos::alt_unit altitude_unit_{};
|
||||
@@ -267,7 +269,7 @@ class GeoMapView : public View {
|
||||
altitude_unit_};
|
||||
|
||||
GeoMap geomap{
|
||||
{0, banner_height, 240, 320 - 16 - banner_height}};
|
||||
{0, GeoMap::banner_height, GeoMap::geomap_rect_width, GeoMap::geomap_rect_height}};
|
||||
|
||||
Button button_ok{
|
||||
{20 * 8, 8, 8 * 8, 2 * 16},
|
||||
|
||||
@@ -42,6 +42,16 @@ FrequencyField::FrequencyField(
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
FrequencyField::FrequencyField(
|
||||
const Point parent_pos,
|
||||
const rf::FrequencyRange range)
|
||||
: Widget{{parent_pos, {8 * 10, 16}}},
|
||||
length_{11},
|
||||
range_{range} {
|
||||
initial_switch_config_ = get_switches_long_press_config();
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
FrequencyField::~FrequencyField() {
|
||||
reset_switch_config();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class FrequencyField : public Widget {
|
||||
using range_t = rf::FrequencyRange;
|
||||
|
||||
FrequencyField(Point parent_pos);
|
||||
FrequencyField(Point parent_pos, rf::FrequencyRange range);
|
||||
~FrequencyField();
|
||||
|
||||
rf::Frequency value() const;
|
||||
@@ -66,8 +67,7 @@ class FrequencyField : public Widget {
|
||||
|
||||
private:
|
||||
const size_t length_;
|
||||
const range_t range_;
|
||||
|
||||
range_t range_;
|
||||
rf::Frequency value_{0};
|
||||
rf::Frequency step_{25000};
|
||||
uint64_t last_ms_{0};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user