mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 02:52:58 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e55444f19 | |||
| 73f7f84718 | |||
| 7ad4ad99dd | |||
| 200f10397b | |||
| 9b352f45a2 | |||
| 51c445054e | |||
| 02586101e6 | |||
| b66d8b1aa1 | |||
| 8dc3851b55 | |||
| aef7c2be93 | |||
| 4e38dc4d3e | |||
| 520ad97f78 | |||
| 5bd208bca1 | |||
| a65ef3ce2e | |||
| b2bb37af74 | |||
| 54ad82a423 | |||
| e463405c77 | |||
| c413f925a9 | |||
| 9e1eb31150 | |||
| 7eb08f34cb | |||
| 2de1f8e6bf | |||
| 55db0e8c87 | |||
| dfa35b4290 | |||
| 0124051c4f | |||
| 65f301399f | |||
| 97889494cf | |||
| 32b35b1507 | |||
| fccff0484b |
@@ -61,6 +61,7 @@ cmake-build-debug/
|
||||
*.sublime-workspace
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
|
||||
# VSCodium extensions
|
||||
.history
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
cmake_policy(SET CMP0005 NEW)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.--->
|
||||
# PortaPack Mayhem
|
||||
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
||||
|
||||
@@ -26,7 +26,7 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
## What to buy?
|
||||
|
||||
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/HR60Q4) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top).
|
||||
:heavy_check_mark:  The fabulous H4M [complete](https://share.hackrf.app/HR60Q4) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
|
||||
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ FROM ubuntu:noble
|
||||
# Set location to download ARM toolkit from.
|
||||
# This will need to be changed over time or replaced with a static link to the latest release.
|
||||
ENV ARMBINURL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2?revision=108bd959-44bd-4619-9c19-26187abf5225&la=en&hash=E788CE92E5DFD64B2A8C246BBA91A249CB8E2D2D \
|
||||
PATH=$HOME/bin:$PATH:/opt/build/armbin/bin
|
||||
PATH=$PATH:/opt/build/armbin/bin
|
||||
|
||||
#Create volume /havoc/bin for compiled firmware binaries
|
||||
VOLUME /havoc
|
||||
@@ -15,8 +15,8 @@ RUN apt-get update \
|
||||
&& apt-get -qy autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
# Grab the GNU ARM toolchain from arm.com
|
||||
# Then extract contents to /opt/build/armbin/
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(firmware)
|
||||
|
||||
|
||||
@@ -305,7 +305,6 @@ set(CPPSRC
|
||||
apps/ui_recon.cpp
|
||||
apps/ui_scanner.cpp
|
||||
apps/ui_sd_over_usb.cpp
|
||||
apps/ui_sd_wipe.cpp
|
||||
apps/ui_search.cpp
|
||||
apps/ui_settings.cpp
|
||||
apps/ui_siggen.cpp
|
||||
@@ -317,9 +316,7 @@ set(CPPSRC
|
||||
apps/ui_text_editor.cpp
|
||||
apps/ui_touch_calibration.cpp
|
||||
apps/ui_touchtunes.cpp
|
||||
apps/ui_view_wav.cpp
|
||||
apps/ui_weatherstation.cpp
|
||||
apps/ui_whipcalc.cpp
|
||||
protocols/aprs.cpp
|
||||
protocols/ax25.cpp
|
||||
protocols/bht.cpp
|
||||
@@ -377,7 +374,7 @@ set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC
|
||||
hw
|
||||
apps
|
||||
protocols
|
||||
bitmaps
|
||||
bmps
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
@@ -39,6 +39,14 @@ using namespace portapack;
|
||||
using namespace modems;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#define BLE_RX_NO_ERROR 0
|
||||
#define BLE_RX_LIST_FILENAME_EMPTY_ERROR 1
|
||||
#define BLE_RX_ENTRY_FILENAME_EMPTY_ERROR 2
|
||||
#define BLE_RX_LIST_SAVE_ERROR 3
|
||||
#define BLE_RX_ENTRY_SAVE_ERROR 4
|
||||
|
||||
static uint8_t ble_rx_error = BLE_RX_NO_ERROR;
|
||||
|
||||
void BLELogger::log_raw_data(const std::string& data) {
|
||||
log_file.write_entry(data);
|
||||
}
|
||||
@@ -202,19 +210,23 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::on_save_file(const std::string value, BLETxPacket packetToSave) {
|
||||
ensure_directory(packet_save_path);
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
|
||||
saveFile(new_path, packetToSave);
|
||||
if (value.length() > 0) {
|
||||
ensure_directory(packet_save_path);
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
ble_rx_error = saveFile(new_path, packetToSave);
|
||||
} else {
|
||||
nav_.pop();
|
||||
ble_rx_error = BLE_RX_ENTRY_FILENAME_EMPTY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETxPacket packetToSave) {
|
||||
File f;
|
||||
auto error = f.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
return BLE_RX_ENTRY_SAVE_ERROR;
|
||||
|
||||
std::string macAddressStr = packetToSave.macAddress;
|
||||
std::string advertisementDataStr = packetToSave.advertisementData;
|
||||
@@ -224,7 +236,7 @@ bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETx
|
||||
|
||||
f.write(packetString.c_str(), packetString.length());
|
||||
|
||||
return true;
|
||||
return BLE_RX_NO_ERROR;
|
||||
}
|
||||
|
||||
void BleRecentEntryDetailView::update_data() {
|
||||
@@ -568,11 +580,14 @@ std::string BLERxView::build_line_str(BleRecentEntry entry) {
|
||||
}
|
||||
|
||||
void BLERxView::on_save_file(const std::string value) {
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
|
||||
saveFile(new_path);
|
||||
if (value.length() > 0) {
|
||||
auto folder = packet_save_path.parent_path();
|
||||
auto ext = packet_save_path.extension();
|
||||
auto new_path = folder / value + ext;
|
||||
ble_rx_error = saveFile(new_path);
|
||||
} else {
|
||||
ble_rx_error = BLE_RX_LIST_FILENAME_EMPTY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
@@ -584,7 +599,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
auto error = src->open(path, false, true);
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
return BLE_RX_LIST_SAVE_ERROR;
|
||||
}
|
||||
|
||||
for (const auto& entry : recent) {
|
||||
@@ -615,7 +630,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
auto error = dst->open(tempFilePath, false, true);
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
return BLE_RX_LIST_SAVE_ERROR;
|
||||
}
|
||||
|
||||
dst->write_line(headerStr.c_str());
|
||||
@@ -695,7 +710,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
|
||||
tempList.clear();
|
||||
|
||||
return true;
|
||||
return BLE_RX_NO_ERROR;
|
||||
}
|
||||
|
||||
void BLERxView::on_data(BlePacketData* packet) {
|
||||
@@ -831,6 +846,18 @@ void BLERxView::on_timer() {
|
||||
baseband::set_btlerx(randomChannel);
|
||||
}
|
||||
}
|
||||
if (ble_rx_error != BLE_RX_NO_ERROR) {
|
||||
if (ble_rx_error == BLE_RX_LIST_FILENAME_EMPTY_ERROR) {
|
||||
nav_.display_modal("Error", "List filename is empty !");
|
||||
} else if (ble_rx_error == BLE_RX_ENTRY_FILENAME_EMPTY_ERROR) {
|
||||
nav_.display_modal("Error", "Entry filename is empty !");
|
||||
} else if (ble_rx_error == BLE_RX_LIST_SAVE_ERROR) {
|
||||
nav_.display_modal("Error", "Couldn't save list !");
|
||||
} else if (ble_rx_error == BLE_RX_ENTRY_SAVE_ERROR) {
|
||||
nav_.display_modal("Error", "Couldn't save entry !");
|
||||
}
|
||||
ble_rx_error = BLE_RX_NO_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void BLERxView::handle_entries_sort(uint8_t index) {
|
||||
|
||||
@@ -7,30 +7,33 @@
|
||||
namespace ui {
|
||||
|
||||
// Information: a line starting with a '#' will be yellow coloured
|
||||
constexpr std::string_view authors_list[] = {
|
||||
"# * List of contributors * ",
|
||||
constexpr std::string_view mayhem_information_list[] = {
|
||||
"#****** Mayhem Community ******",
|
||||
" ",
|
||||
" https://discord.mayhem.app",
|
||||
" ",
|
||||
"#**** List of contributors ****",
|
||||
" ",
|
||||
"#Mayhem-Firmware:",
|
||||
"jboone,eried,furrtek,",
|
||||
"NotherNgineer,gullradriel,",
|
||||
"jLynx,kallanreed,Brumi-2021,",
|
||||
"htotoo,bernd-herzog,zxkmm,",
|
||||
"htotoo,zxkmm,bernd-herzog,",
|
||||
"ArjanOnwezen,euquiq,u-foka,",
|
||||
"iNetro,heurist1,dqs105,",
|
||||
"teixeluis,jwetzell,",
|
||||
"jimilinuxguy,gregoryfenton,",
|
||||
"notpike,strijar,BehleZebub,",
|
||||
"arneluehrs,rascafr,joyel24,",
|
||||
"ImDroided,zigad,johnelder,",
|
||||
"klockee,nnesetto,LupusE,",
|
||||
"argilo,dc2dc,formtapez,",
|
||||
"arneluehrs,mcules,rascafr,",
|
||||
"joyel24,ImDroided,zigad,",
|
||||
"johnelder,klockee,nnesetto,",
|
||||
"LupusE,argilo,dc2dc,formtapez,",
|
||||
"RocketGod-git,mrmookie,",
|
||||
"ITAxReal,F33RNI,F4GEV,",
|
||||
"rusty-labs,mjwaxios,andrej-mk,",
|
||||
"RedFox-Fr,nemanjan00,",
|
||||
"MichalLeonBorsuk,",
|
||||
"MatiasFernandez,Giorgiofox,",
|
||||
"ckuethe",
|
||||
"MatiasFernandez,Giorgiofox",
|
||||
" ",
|
||||
"#Havoc:",
|
||||
"jboone,furrtek,eried,argilo,",
|
||||
@@ -72,7 +75,7 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
button_ok.focus();
|
||||
};
|
||||
|
||||
for (auto& authors_line : authors_list) {
|
||||
for (auto& authors_line : mayhem_information_list) {
|
||||
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||
if (authors_line.size() > 0) {
|
||||
if (authors_line[0] == '#') {
|
||||
@@ -103,15 +106,13 @@ void AboutView::on_frame_sync() {
|
||||
menu_view.set_highlighted(current + 1);
|
||||
} else {
|
||||
menu_view.set_highlighted(0);
|
||||
// ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
menu_view.set_highlighted(3); // contributors block starting line
|
||||
}
|
||||
|
||||
bool AboutView::on_touch(const TouchEvent) {
|
||||
|
||||
@@ -99,13 +99,21 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
|
||||
field_frequency.set_value(aprs_rx_freq);
|
||||
} else if (i == 1) { // NA - North America - is also the default
|
||||
field_frequency.set_value(144390000);
|
||||
} else if (i == 2) { // EUR
|
||||
field_frequency.set_value(144800000);
|
||||
} else if (i == 3) { // AUS
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 4) { // NZ
|
||||
} else if (i == 2) { // NZ
|
||||
field_frequency.set_value(144575000);
|
||||
} else if (i == 5) { // ISS
|
||||
} else if (i == 3) { // JAP
|
||||
field_frequency.set_value(144640000);
|
||||
} else if (i == 4) { // PHI
|
||||
field_frequency.set_value(144740000);
|
||||
} else if (i == 5) { // EUR
|
||||
field_frequency.set_value(144800000);
|
||||
} else if (i == 6) { // THA
|
||||
field_frequency.set_value(144900000);
|
||||
} else if (i == 7) { // AUS
|
||||
field_frequency.set_value(145175000);
|
||||
} else if (i == 8) { // BR
|
||||
field_frequency.set_value(145570000);
|
||||
} else if (i == 9) { // ISS
|
||||
field_frequency.set_value(145825000);
|
||||
}
|
||||
options_region_id = i;
|
||||
|
||||
@@ -227,10 +227,14 @@ class APRSRxView : public View {
|
||||
3,
|
||||
{{"MAN", 0},
|
||||
{"NA ", 1},
|
||||
{"EUR", 2},
|
||||
{"AUS", 3},
|
||||
{"NZ ", 4},
|
||||
{"ISS", 5}}};
|
||||
{"NZ ", 2},
|
||||
{"JAP", 3},
|
||||
{"PHI", 4},
|
||||
{"EUR", 5},
|
||||
{"THA", 6},
|
||||
{"AUS", 7},
|
||||
{"BR ", 8},
|
||||
{"ISS", 9}}};
|
||||
|
||||
FrequencyField field_frequency{
|
||||
{3 * 8, 0 * 16}};
|
||||
|
||||
@@ -177,4 +177,4 @@ void FlashUtilityView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -711,10 +711,12 @@ SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
&button_dial_sensitivity_plus,
|
||||
&button_dial_sensitivity_minus,
|
||||
&button_rate_multiplier_plus,
|
||||
&button_rate_multiplier_minus});
|
||||
&button_rate_multiplier_minus,
|
||||
&field_encoder_dial_direction});
|
||||
|
||||
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
|
||||
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
|
||||
field_encoder_dial_direction.set_by_value(pmem::encoder_dial_direction());
|
||||
|
||||
button_dial_sensitivity_plus.on_select = [this](Button&) {
|
||||
field_encoder_dial_sensitivity.on_encoder(1);
|
||||
@@ -732,6 +734,7 @@ SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
|
||||
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
|
||||
pmem::set_encoder_dial_direction(field_encoder_dial_direction.selected_index_value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
|
||||
@@ -574,6 +574,7 @@ class SetQRCodeView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
using portapack::persistent_memory::encoder_dial_direction;
|
||||
using portapack::persistent_memory::encoder_dial_sensitivity;
|
||||
using portapack::persistent_memory::encoder_rate_multiplier;
|
||||
|
||||
@@ -587,44 +588,50 @@ class SetEncoderDialView : public View {
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 2 * 16}, "rotation position (number of", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "steps per full rotation):", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Dial sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 9 * 16}, "rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 10 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 12 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 0 * 16}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 3 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
|
||||
|
||||
};
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 5 * 16},
|
||||
{20 * 8, 3 * 16},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
|
||||
NumberField field_encoder_rate_multiplier{
|
||||
{20 * 8, 12 * 16},
|
||||
{20 * 8, 10 * 16},
|
||||
2,
|
||||
{1, 15},
|
||||
1,
|
||||
' '};
|
||||
|
||||
OptionsField field_encoder_dial_direction{
|
||||
{18 * 8, 14 * 16},
|
||||
7,
|
||||
{{"NORMAL", false},
|
||||
{"REVERSE", true}}};
|
||||
|
||||
Button button_dial_sensitivity_plus{
|
||||
{20 * 8, 4 * 16, 16, 16},
|
||||
{20 * 8, 2 * 16, 16, 16},
|
||||
"+"};
|
||||
|
||||
Button button_dial_sensitivity_minus{
|
||||
{20 * 8, 6 * 16, 16, 16},
|
||||
{20 * 8, 4 * 16, 16, 16},
|
||||
"-"};
|
||||
|
||||
Button button_rate_multiplier_plus{
|
||||
{20 * 8, 11 * 16, 16, 16},
|
||||
{20 * 8, 9 * 16, 16, 16},
|
||||
"+"};
|
||||
|
||||
Button button_rate_multiplier_minus{
|
||||
{20 * 8, 13 * 16, 16, 16},
|
||||
{20 * 8, 11 * 16, 16, 16},
|
||||
"-"};
|
||||
|
||||
Button button_save{
|
||||
|
||||
@@ -44,9 +44,9 @@ SigGenView::~SigGenView() {
|
||||
|
||||
void SigGenView::update_config() {
|
||||
if (checkbox_stop.value())
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), field_stop.value());
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), field_stop.value());
|
||||
else
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), 0);
|
||||
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), 0);
|
||||
}
|
||||
|
||||
void SigGenView::update_tone() {
|
||||
@@ -78,6 +78,7 @@ SigGenView::SigGenView(
|
||||
baseband::run_image(portapack::spi_flash::image_tag_siggen);
|
||||
|
||||
add_children({&labels,
|
||||
&options_mod,
|
||||
&options_shape,
|
||||
&text_shape,
|
||||
&symfield_tone,
|
||||
@@ -87,22 +88,48 @@ SigGenView::SigGenView(
|
||||
&field_stop,
|
||||
&tx_view});
|
||||
|
||||
symfield_tone.hidden(1); // At first launch , by default we are in CW Shape has NO MOD , we are not using Tone modulation.
|
||||
symfield_tone.hidden(true); // At first launch , by default we are in CW: Shape ignored, we are not using Tone modulation.
|
||||
options_shape.hidden(true);
|
||||
text_shape.hidden(true);
|
||||
symfield_tone.set_value(1000); // Default: 1000 Hz
|
||||
options_shape.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
text_shape.set(shape_strings[v]);
|
||||
if (auto_update)
|
||||
update_config();
|
||||
if ((v == 0) || (v == 6)) { // In Shapes Options (CW & Pseudo Random Noise) we are not using Tone modulation freq.
|
||||
symfield_tone.hidden(1);
|
||||
|
||||
if (v == 5) { // In Shape Pseudo Random Noise we are not using Tone modulation freq.
|
||||
symfield_tone.hidden(true);
|
||||
} else {
|
||||
symfield_tone.hidden(0);
|
||||
symfield_tone.hidden(false);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
};
|
||||
options_shape.set_selected_index(0);
|
||||
text_shape.set(shape_strings[0]);
|
||||
|
||||
options_mod.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
if (auto_update)
|
||||
update_config();
|
||||
|
||||
if (v == 0) { // In Modulation Options CW we are not using Tone modulation freq.
|
||||
symfield_tone.hidden(true);
|
||||
} else {
|
||||
symfield_tone.hidden(false);
|
||||
}
|
||||
|
||||
if ((v == 0) || (v == 2) || (v == 3)) { // In Modulation Options CW, QPSK, BPSK we are not using Shapes.
|
||||
options_shape.hidden(true);
|
||||
text_shape.hidden(true);
|
||||
} else {
|
||||
options_shape.hidden(false);
|
||||
text_shape.hidden(false);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
};
|
||||
options_mod.set_selected_index(0);
|
||||
|
||||
field_stop.set_value(1);
|
||||
|
||||
symfield_tone.set_value(1000); // Default: 1000 Hz
|
||||
|
||||
@@ -58,68 +58,73 @@ class SigGenView : public View {
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_siggen", app_settings::Mode::TX};
|
||||
|
||||
const std::string shape_strings[9] = {
|
||||
"CW (No mod.) ",
|
||||
"Sine mod. FM",
|
||||
"Triangle mod.FM", // max 15 character text space.
|
||||
"Saw up mod. FM",
|
||||
"Saw down mod.FM",
|
||||
"Square mod. FM",
|
||||
"Pseudo Noise FM", // using 16 bits LFSR register, 16 order polynomial feedback.
|
||||
"BPSK 0,1,0,1...",
|
||||
"QPSK 00-01-10.."};
|
||||
const std::string shape_strings[6] = {// max 15 character text space.
|
||||
"Sine",
|
||||
"Triangle",
|
||||
"Saw up",
|
||||
"Saw down",
|
||||
"Square",
|
||||
"Pseudo Noise"};
|
||||
|
||||
bool auto_update{false};
|
||||
|
||||
Labels labels{
|
||||
{{3 * 8, 4 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
|
||||
{{6 * 8, 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
|
||||
{{22 * 8, 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground},
|
||||
{{8 * 8, 20 * 8}, "Modulation: FM", Theme::getInstance()->fg_light->foreground}};
|
||||
{{3 * 8, 2 * 8}, "Modulation:", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 3 * 8 + 8 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
|
||||
{{6 * 8, 2 * 8 + 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
|
||||
{{22 * 8, 2 * 8 + 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
ImageOptionsField options_shape{
|
||||
{10 * 8, 4, 32, 32},
|
||||
{10 * 8, 3 * 8 + 8, 32, 32},
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
Theme::getInstance()->bg_darkest->background,
|
||||
{{&bitmap_sig_cw, 0},
|
||||
{&bitmap_sig_sine, 1},
|
||||
{&bitmap_sig_tri, 2},
|
||||
{&bitmap_sig_saw_up, 3},
|
||||
{&bitmap_sig_saw_down, 4},
|
||||
{&bitmap_sig_square, 5},
|
||||
{&bitmap_sig_noise, 6},
|
||||
{&bitmap_sig_noise, 7}, // Pending to add a correct BPSK icon.
|
||||
{&bitmap_sig_noise, 8}}}; // Pending to add a correct QPSK icon.
|
||||
{{&bitmap_sig_sine, 0},
|
||||
{&bitmap_sig_tri, 1},
|
||||
{&bitmap_sig_saw_up, 2},
|
||||
{&bitmap_sig_saw_down, 3},
|
||||
{&bitmap_sig_square, 4},
|
||||
{&bitmap_sig_noise, 5}}};
|
||||
|
||||
Text text_shape{
|
||||
{15 * 8, 4 + 10, 15 * 8, 16},
|
||||
{15 * 8, 3 * 8 + 8 + 10, 15 * 8, 16},
|
||||
""};
|
||||
|
||||
SymField symfield_tone{
|
||||
{12 * 8, 7 * 8},
|
||||
{12 * 8, 2 * 8 + 7 * 8},
|
||||
5};
|
||||
|
||||
Button button_update{
|
||||
{5 * 8, 10 * 8, 8 * 8, 3 * 8},
|
||||
{5 * 8, 2 * 8 + 10 * 8, 8 * 8, 3 * 8},
|
||||
"Update"};
|
||||
|
||||
Checkbox checkbox_auto{
|
||||
{15 * 8, 10 * 8},
|
||||
{15 * 8, 2 * 8 + 10 * 8},
|
||||
4,
|
||||
"Auto"};
|
||||
|
||||
Checkbox checkbox_stop{
|
||||
{5 * 8, 15 * 8},
|
||||
{5 * 8, 2 * 8 + 15 * 8},
|
||||
10,
|
||||
"Stop after"};
|
||||
|
||||
NumberField field_stop{
|
||||
{20 * 8, 15 * 8 + 4},
|
||||
{20 * 8, 2 * 8 + 15 * 8 + 4},
|
||||
2,
|
||||
{1, 99},
|
||||
1,
|
||||
' '};
|
||||
|
||||
OptionsField options_mod{
|
||||
{15 * 8, 2 * 8},
|
||||
12,
|
||||
{{"CW (No mod.)", 0},
|
||||
{"FM", 1},
|
||||
{"BPSK", 2},
|
||||
{"QPSK", 3},
|
||||
{"DSB", 4},
|
||||
{"AM 100% dep.", 5},
|
||||
{"AM 50% depth", 6}}};
|
||||
|
||||
TransmitterView tx_view{
|
||||
16 * 16,
|
||||
10000,
|
||||
|
||||
@@ -987,44 +987,6 @@ static constexpr Bitmap bitmap_icon_previous{
|
||||
{16, 16},
|
||||
bitmap_icon_previous_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_tools_antenna_data[] = {
|
||||
0x38,
|
||||
0x3E,
|
||||
0x10,
|
||||
0x22,
|
||||
0x10,
|
||||
0x26,
|
||||
0x10,
|
||||
0x22,
|
||||
0x10,
|
||||
0x2E,
|
||||
0x10,
|
||||
0x22,
|
||||
0x10,
|
||||
0x26,
|
||||
0x10,
|
||||
0x22,
|
||||
0x38,
|
||||
0x2E,
|
||||
0x38,
|
||||
0x22,
|
||||
0x38,
|
||||
0x26,
|
||||
0x38,
|
||||
0x22,
|
||||
0x38,
|
||||
0x2E,
|
||||
0x38,
|
||||
0x22,
|
||||
0x38,
|
||||
0x3E,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_tools_antenna{
|
||||
{16, 16},
|
||||
bitmap_icon_tools_antenna_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_batt_text_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
@@ -1799,6 +1761,45 @@ static constexpr uint8_t bitmap_icon_lge_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_file_iq_data[] = {
|
||||
0xFC,
|
||||
0x03,
|
||||
0x04,
|
||||
0x06,
|
||||
0x04,
|
||||
0x0E,
|
||||
0x04,
|
||||
0x1E,
|
||||
0x04,
|
||||
0x3E,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x21,
|
||||
0x44,
|
||||
0x25,
|
||||
0x54,
|
||||
0x25,
|
||||
0xF4,
|
||||
0x2F,
|
||||
0xA4,
|
||||
0x2A,
|
||||
0x84,
|
||||
0x22,
|
||||
0x04,
|
||||
0x22,
|
||||
0x04,
|
||||
0x20,
|
||||
0xFC,
|
||||
0x3F,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_file_iq{
|
||||
{16, 16},
|
||||
bitmap_icon_file_iq_data};
|
||||
|
||||
static constexpr Bitmap bitmap_icon_lge{
|
||||
{16, 16},
|
||||
bitmap_icon_lge_data};
|
||||
@@ -2713,44 +2714,6 @@ static constexpr Bitmap bitmap_icon_touchtunes{
|
||||
{16, 16},
|
||||
bitmap_icon_touchtunes_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_file_iq_data[] = {
|
||||
0xFC,
|
||||
0x03,
|
||||
0x04,
|
||||
0x06,
|
||||
0x04,
|
||||
0x0E,
|
||||
0x04,
|
||||
0x1E,
|
||||
0x04,
|
||||
0x3E,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x20,
|
||||
0x04,
|
||||
0x21,
|
||||
0x44,
|
||||
0x25,
|
||||
0x54,
|
||||
0x25,
|
||||
0xF4,
|
||||
0x2F,
|
||||
0xA4,
|
||||
0x2A,
|
||||
0x84,
|
||||
0x22,
|
||||
0x04,
|
||||
0x22,
|
||||
0x04,
|
||||
0x20,
|
||||
0xFC,
|
||||
0x3F,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_file_iq{
|
||||
{16, 16},
|
||||
bitmap_icon_file_iq_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_sd_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* 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_whipcalc.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::antenna_length {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<WhipCalcView>();
|
||||
}
|
||||
} // namespace ui::external_app::antenna_length
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_antenna_length.application_information"), used)) application_information_t _application_information_antenna_length = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::antenna_length::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Antenna Length",
|
||||
/*.bitmap_data = */ {0x38, 0x3E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x10, 0x2E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x26, 0x38, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x3E, 0x00, 0x00},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+3
-2
@@ -32,8 +32,9 @@
|
||||
#include <cstring>
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::antenna_length {
|
||||
|
||||
void WhipCalcView::focus() {
|
||||
field_frequency.focus();
|
||||
@@ -176,4 +177,4 @@ void WhipCalcView::load_antenna_db() {
|
||||
void WhipCalcView::add_default_antenna() {
|
||||
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
|
||||
}
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::antenna_length
|
||||
+5
-2
@@ -31,7 +31,10 @@
|
||||
#include "string_format.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::antenna_length {
|
||||
|
||||
class WhipCalcView : public View {
|
||||
public:
|
||||
WhipCalcView(NavigationView& nav);
|
||||
@@ -90,6 +93,6 @@ class WhipCalcView : public View {
|
||||
"Back"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::antenna_length
|
||||
|
||||
#endif /*__UI_WHIPCALC__*/
|
||||
+26
-1
@@ -150,6 +150,26 @@ set(EXTCPPSRC
|
||||
#app_manager
|
||||
external/app_manager/main.cpp
|
||||
external/app_manager/ui_app_manager.cpp
|
||||
|
||||
#hopper
|
||||
external/hopper/main.cpp
|
||||
external/hopper/ui_hopper.cpp
|
||||
|
||||
# whip calculator
|
||||
external/antenna_length/main.cpp
|
||||
external/antenna_length/ui_whipcalc.cpp
|
||||
|
||||
# wav viewer
|
||||
external/wav_view/main.cpp
|
||||
external/wav_view/ui_view_wav.cpp
|
||||
|
||||
# wipe sdcard
|
||||
external/sd_wipe/main.cpp
|
||||
external/sd_wipe/ui_sd_wipe.cpp
|
||||
|
||||
# playlist editor
|
||||
external/playlist_editor/main.cpp
|
||||
external/playlist_editor/ui_playlist_editor.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -189,4 +209,9 @@ set(EXTAPPLIST
|
||||
tuner
|
||||
metronome
|
||||
app_manager
|
||||
)
|
||||
hopper
|
||||
antenna_length
|
||||
view_wav
|
||||
sd_wipe
|
||||
playlist_editor
|
||||
)
|
||||
|
||||
+35
@@ -59,6 +59,11 @@ MEMORY
|
||||
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
|
||||
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
|
||||
ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k
|
||||
ram_external_app_hopper(rwx) : org = 0xADD50000, len = 32k
|
||||
ram_external_app_antenna_length(rwx) : org = 0xADD60000, len = 32k
|
||||
ram_external_app_view_wav(rwx) : org = 0xADD70000, len = 32k
|
||||
ram_external_app_sd_wipe(rwx) : org = 0xADD80000, len = 32k
|
||||
ram_external_app_playlist_editor(rwx) : org = 0xADD90000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -279,4 +284,34 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_app_manager.application_information));
|
||||
*(*ui*external_app*app_manager*);
|
||||
} > ram_external_app_app_manager
|
||||
|
||||
.external_app_hopper : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_hopper.application_information));
|
||||
*(*ui*external_app*hopper*);
|
||||
} > ram_external_app_hopper
|
||||
|
||||
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_antenna_length.application_information));
|
||||
*(*ui*external_app*antenna_length*);
|
||||
} > ram_external_app_antenna_length
|
||||
|
||||
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_view_wav.application_information));
|
||||
*(*ui*external_app*view_wav*);
|
||||
} > ram_external_app_view_wav
|
||||
|
||||
.external_app_sd_wipe : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_sd_wipe.application_information));
|
||||
*(*ui*external_app*sd_wipe*);
|
||||
} > ram_external_app_sd_wipe
|
||||
|
||||
.external_app_playlist_editor : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_playlist_editor.application_information));
|
||||
*(*ui*external_app*playlist_editor*);
|
||||
} > ram_external_app_playlist_editor
|
||||
}
|
||||
|
||||
@@ -89,8 +89,6 @@ class FlipperTxView : public View {
|
||||
std::unique_ptr<FlipperPlayThread> replay_thread{};
|
||||
Optional<flippersub_metadata> submeta{};
|
||||
|
||||
const std::filesystem::path subghz_dir = u"subghz";
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* 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_hopper.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::hopper {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<HopperView>();
|
||||
}
|
||||
} // namespace ui::external_app::hopper
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_hopper.application_information"), used)) application_information_t _application_information_hopper = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::hopper::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Hopper",
|
||||
/*.bitmap_data = */ {
|
||||
0xE0,
|
||||
0x07,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0x1C,
|
||||
0x38,
|
||||
0x0E,
|
||||
0x78,
|
||||
0x06,
|
||||
0x7C,
|
||||
0x03,
|
||||
0xCE,
|
||||
0x03,
|
||||
0xC7,
|
||||
0x83,
|
||||
0xC3,
|
||||
0xC3,
|
||||
0xC1,
|
||||
0xE3,
|
||||
0xC0,
|
||||
0x73,
|
||||
0xC0,
|
||||
0x3E,
|
||||
0x60,
|
||||
0x1E,
|
||||
0x70,
|
||||
0x1C,
|
||||
0x38,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0xE0,
|
||||
0x07,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_jammer */ {'P', 'J', 'A', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2020 euquiq
|
||||
* copyleft mr.r0b0t from the F society
|
||||
*
|
||||
* 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_hopper.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "convert.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::hopper {
|
||||
|
||||
void HopperView::focus() {
|
||||
button_load_list.focus();
|
||||
}
|
||||
|
||||
HopperView::~HopperView() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void HopperView::update_freq_list_menu_view() {
|
||||
menu_freq_list.clear();
|
||||
uint8_t list_count = freq_list.size();
|
||||
|
||||
if (list_count == 0) {
|
||||
menu_freq_list.add_item({"Add freq or load list",
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < list_count; i++) {
|
||||
menu_freq_list.add_item({to_string_rounded_freq(freq_list[i], INT8_MAX),
|
||||
Theme::getInstance()->bg_darkest->foreground,
|
||||
nullptr,
|
||||
nullptr});
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void HopperView::on_retune(const rf::Frequency freq, const uint32_t range) {
|
||||
if (freq) {
|
||||
transmitter_model.set_target_frequency(freq);
|
||||
text_range_number.set(to_string_dec_uint(range, 2));
|
||||
}
|
||||
}
|
||||
|
||||
void HopperView::set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration) {
|
||||
hopper_channels[i].enabled = true;
|
||||
hopper_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
|
||||
hopper_channels[i].center = center;
|
||||
hopper_channels[i].duration = 30720 * duration;
|
||||
}
|
||||
|
||||
void HopperView::start_tx() {
|
||||
uint32_t c, i = 0;
|
||||
bool out_of_ranges = false;
|
||||
|
||||
size_t hop_value = options_hop.selected_index_value();
|
||||
|
||||
// Disable all channels by default
|
||||
for (c = 0; c < JAMMER_MAX_CH; c++)
|
||||
hopper_channels[c].enabled = false;
|
||||
|
||||
uint8_t channel_count = freq_list.size();
|
||||
|
||||
if (channel_count <= JAMMER_MAX_CH) {
|
||||
for (c = 0; c < channel_count; c++) {
|
||||
if (i >= JAMMER_MAX_CH) {
|
||||
out_of_ranges = true;
|
||||
break;
|
||||
}
|
||||
set_hopper_channel(i, JAMMER_CH_WIDTH, freq_list[c], hop_value);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
out_of_ranges = true;
|
||||
}
|
||||
|
||||
if (!out_of_ranges && i) {
|
||||
text_range_total.set("/" + to_string_dec_uint(i, 2));
|
||||
|
||||
jamming = true;
|
||||
button_transmit.set_style(&style_cancel);
|
||||
button_transmit.set_text("STOP");
|
||||
|
||||
transmitter_model.set_rf_amp(field_amp.value());
|
||||
transmitter_model.set_tx_gain(field_gain.value());
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
|
||||
transmitter_model.enable();
|
||||
|
||||
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
mscounter = 0; // euquiq: Reset internal ms counter for do_timer()
|
||||
} else {
|
||||
if (out_of_ranges)
|
||||
nav_.display_modal("Error", "Jam freq too much.");
|
||||
}
|
||||
}
|
||||
|
||||
void HopperView::stop_tx() {
|
||||
button_transmit.set_style(&style_val);
|
||||
button_transmit.set_text("START");
|
||||
transmitter_model.disable();
|
||||
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
|
||||
jamming = false;
|
||||
cooling = false;
|
||||
}
|
||||
|
||||
// called each 1/60th of second
|
||||
void HopperView::on_timer() {
|
||||
if (++mscounter == 60) {
|
||||
mscounter = 0;
|
||||
if (jamming) {
|
||||
if (cooling) {
|
||||
if (++seconds >= field_timepause.value()) { // Re-start TX
|
||||
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
|
||||
transmitter_model.enable();
|
||||
button_transmit.set_text("STOP");
|
||||
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
|
||||
|
||||
int32_t jitter_amount = field_jitter.value();
|
||||
if (jitter_amount) {
|
||||
lfsr_v = lfsr_iterate(lfsr_v);
|
||||
jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
|
||||
mscounter += jitter_amount;
|
||||
}
|
||||
|
||||
cooling = false;
|
||||
seconds = 0;
|
||||
}
|
||||
} else {
|
||||
if (++seconds >= field_timetx.value()) // Start cooling period:
|
||||
{
|
||||
transmitter_model.disable();
|
||||
button_transmit.set_text("PAUSED");
|
||||
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
|
||||
|
||||
int32_t jitter_amount = field_jitter.value();
|
||||
if (jitter_amount) {
|
||||
lfsr_v = lfsr_iterate(lfsr_v);
|
||||
jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
|
||||
mscounter += jitter_amount;
|
||||
}
|
||||
|
||||
cooling = true;
|
||||
seconds = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HopperView::load_list() {
|
||||
freq_list.clear();
|
||||
|
||||
auto open_view = nav_.push<FileLoadView>(".PHOP");
|
||||
open_view->push_dir(hopper_dir);
|
||||
open_view->on_changed = [this](std::filesystem::path path) {
|
||||
File f;
|
||||
auto error = f.open(path);
|
||||
if (error) {
|
||||
nav_.display_modal("Err", "Can't open.");
|
||||
update_freq_list_menu_view();
|
||||
return;
|
||||
}
|
||||
|
||||
freq_list.clear();
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
for (const auto& line : reader) {
|
||||
if (line.length() == 0 || line[0] == '#')
|
||||
continue;
|
||||
|
||||
rf::Frequency freq;
|
||||
if (parse_int(line, freq)) {
|
||||
if (freq_list.size() >= JAMMER_MAX_CH) {
|
||||
break;
|
||||
}
|
||||
freq_list.push_back(freq);
|
||||
}
|
||||
}
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
update_freq_list_menu_view();
|
||||
}
|
||||
|
||||
void HopperView::save_list() {
|
||||
if (freq_list.empty()) {
|
||||
nav_.display_modal("Err", "Nothing to save");
|
||||
return;
|
||||
}
|
||||
|
||||
ensure_directory(hopper_dir);
|
||||
|
||||
filename_buffer = "";
|
||||
text_prompt(
|
||||
nav_,
|
||||
filename_buffer,
|
||||
64,
|
||||
[this](std::string& value) {
|
||||
auto path = hopper_dir / (value + ".PHOP");
|
||||
|
||||
File f;
|
||||
auto error = f.create(path);
|
||||
if (error) {
|
||||
nav_.display_modal("Err", "Create fail.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& freq : freq_list) {
|
||||
auto freq_str = to_string_dec_uint(freq) + "\n";
|
||||
f.write(freq_str.c_str(), freq_str.length());
|
||||
}
|
||||
|
||||
text_range_number.set("Saved: " + path.filename().string());
|
||||
});
|
||||
}
|
||||
|
||||
HopperView::HopperView(
|
||||
NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
// baseband::run_image(portapack::spi_flash::image_tag_jammer);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({&menu_freq_list,
|
||||
&button_load_list,
|
||||
&button_add_freq,
|
||||
&button_delete_freq,
|
||||
&button_save_list,
|
||||
&button_clear,
|
||||
&labels,
|
||||
&options_type,
|
||||
&text_range_number,
|
||||
&text_range_total,
|
||||
&options_speed,
|
||||
&options_hop,
|
||||
&field_timetx,
|
||||
&field_timepause,
|
||||
&field_jitter,
|
||||
&field_gain,
|
||||
&field_amp,
|
||||
&button_transmit});
|
||||
|
||||
options_type.set_selected_index(3); // Rand CW
|
||||
options_speed.set_selected_index(3); // 10kHz
|
||||
options_hop.set_selected_index(1); // 50ms
|
||||
button_transmit.set_style(&style_val);
|
||||
|
||||
field_timetx.set_value(30);
|
||||
field_timepause.set_value(1);
|
||||
field_gain.set_value(transmitter_model.tx_gain());
|
||||
field_amp.set_value(transmitter_model.rf_amp());
|
||||
|
||||
button_load_list.on_select = [this]() {
|
||||
load_list();
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_add_freq.on_select = [this]() {
|
||||
if (freq_list.size() < JAMMER_MAX_CH) {
|
||||
auto kb_view = nav_.push<FrequencyKeypadView>(0);
|
||||
kb_view->on_changed = [this](rf::Frequency freq) {
|
||||
freq_list.push_back(freq);
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
} else {
|
||||
nav_.display_modal("Err", "No more.");
|
||||
}
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_delete_freq.on_select = [this]() {
|
||||
auto i = menu_freq_list.highlighted_index();
|
||||
if (i < freq_list.size()) {
|
||||
freq_list.erase(freq_list.begin() + i);
|
||||
}
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_save_list.on_select = [this]() {
|
||||
save_list();
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_clear.on_select = [this]() {
|
||||
// clang-format off
|
||||
nav_.display_modal("Del:", "Clean all?\n", YESNO, [this](bool choice) {
|
||||
if (choice){
|
||||
freq_list.clear();
|
||||
update_freq_list_menu_view();
|
||||
} }, TRUE);
|
||||
// clang-format on
|
||||
|
||||
update_freq_list_menu_view();
|
||||
};
|
||||
|
||||
button_transmit.on_select = [this](Button&) {
|
||||
if (jamming || cooling)
|
||||
stop_tx();
|
||||
else
|
||||
start_tx();
|
||||
};
|
||||
|
||||
menu_freq_list.on_left = [this]() {
|
||||
button_load_list.focus();
|
||||
};
|
||||
|
||||
menu_freq_list.on_right = [this]() {
|
||||
button_load_list.focus();
|
||||
};
|
||||
|
||||
update_freq_list_menu_view();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::hopper
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2020 euquiq
|
||||
* copyleft mr.r0b0t from the F society
|
||||
*
|
||||
* 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_language.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_tabview.hpp"
|
||||
#include "transmitter_model.hpp"
|
||||
#include "message.hpp"
|
||||
#include "jammer.hpp"
|
||||
#include "lfsr_random.hpp"
|
||||
#include "radio_state.hpp"
|
||||
|
||||
using namespace jammer;
|
||||
|
||||
namespace ui::external_app::hopper {
|
||||
|
||||
class HopperView : public View {
|
||||
public:
|
||||
HopperView(NavigationView& nav);
|
||||
~HopperView();
|
||||
|
||||
HopperView(const HopperView&) = delete;
|
||||
HopperView(HopperView&&) = delete;
|
||||
HopperView& operator=(const HopperView&) = delete;
|
||||
HopperView& operator=(HopperView&&) = delete;
|
||||
|
||||
void focus() override;
|
||||
void update_freq_list_menu_view();
|
||||
|
||||
std::string title() const override { return "Hopper"; };
|
||||
|
||||
std::vector<rf::Frequency> freq_list{315000000, 433920000};
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
3500000 /* bandwidth */,
|
||||
3072000 /* sampling rate */
|
||||
};
|
||||
|
||||
void start_tx();
|
||||
void on_timer();
|
||||
void stop_tx();
|
||||
void set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
|
||||
void on_retune(const rf::Frequency freq, const uint32_t range);
|
||||
void load_list();
|
||||
void save_list();
|
||||
void on_file_save_write(const std::string& value);
|
||||
|
||||
HopperChannel* hopper_channels = (HopperChannel*)shared_memory.bb_data.data;
|
||||
bool jamming{false};
|
||||
bool cooling{false}; // euquiq: Indicates jammer in cooldown
|
||||
uint16_t seconds = 0; // euquiq: seconds counter for toggling tx / cooldown
|
||||
int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
|
||||
lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
|
||||
|
||||
std::string filename_buffer = "";
|
||||
|
||||
const Style& style_val = *Theme::getInstance()->fg_green;
|
||||
const Style& style_cancel = *Theme::getInstance()->fg_red;
|
||||
|
||||
MenuView menu_freq_list{
|
||||
{0, 0, screen_width, 8 * 16},
|
||||
false};
|
||||
|
||||
NewButton button_load_list{
|
||||
{0 * 8, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_load,
|
||||
Color::dark_blue(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_save_list{
|
||||
{4 * 8, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_save,
|
||||
Color::dark_blue(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_add_freq{
|
||||
{8 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_add,
|
||||
Color::dark_green(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_delete_freq{
|
||||
{12 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_trash,
|
||||
Color::dark_red(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
NewButton button_clear{
|
||||
{screen_width - 4 * 8, 9 * 16 + 4, 4 * 8, 32},
|
||||
{},
|
||||
&bitmap_icon_tools_wipesd,
|
||||
Color::red(),
|
||||
/*vcenter*/ true};
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 23 * 8}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 25 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground},
|
||||
{{3 * 8, 27 * 8}, "Hop:", Theme::getInstance()->fg_light->foreground},
|
||||
{{4 * 8, 29 * 8}, "TX:", Theme::getInstance()->fg_light->foreground},
|
||||
{{1 * 8, 31 * 8}, "Sle3p:", Theme::getInstance()->fg_light->foreground}, // euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
|
||||
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground}, // Maybe the repository curator can keep the "mystype" for some versions.
|
||||
{{11 * 8, 29 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 31 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 33 * 8}, "/60", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 35 * 8}, "Gain:", Theme::getInstance()->fg_light->foreground},
|
||||
{{11 * 8, 35 * 8}, "A:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
OptionsField options_type{
|
||||
{7 * 8, 23 * 8},
|
||||
8,
|
||||
{
|
||||
{"Rand FSK", 0},
|
||||
{"FM tone", 1},
|
||||
{"CW sweep", 2},
|
||||
{"Rand CW", 3},
|
||||
}};
|
||||
|
||||
Text text_range_number{
|
||||
{16 * 8, 23 * 8, 2 * 8, 16},
|
||||
"--"};
|
||||
Text text_range_total{
|
||||
{18 * 8, 23 * 8, 3 * 8, 16},
|
||||
"/--"};
|
||||
|
||||
OptionsField options_speed{
|
||||
{7 * 8, 25 * 8},
|
||||
6,
|
||||
{{"10Hz ", 10},
|
||||
{"100Hz ", 100},
|
||||
{"1kHz ", 1000},
|
||||
{"10kHz ", 10000},
|
||||
{"100kHz", 100000}}};
|
||||
|
||||
OptionsField options_hop{
|
||||
{7 * 8, 27 * 8},
|
||||
5,
|
||||
{{"10ms ", 1},
|
||||
{"50ms ", 5},
|
||||
{"100ms", 10},
|
||||
{"1s ", 100},
|
||||
{"2s ", 200},
|
||||
{"5s ", 500},
|
||||
{"10s ", 1000}}};
|
||||
|
||||
NumberField field_timetx{
|
||||
{7 * 8, 29 * 8},
|
||||
3,
|
||||
{1, 180},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_timepause{
|
||||
{8 * 8, 31 * 8},
|
||||
2,
|
||||
{1, 60},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_jitter{
|
||||
{8 * 8, 33 * 8},
|
||||
2,
|
||||
{1, 60},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_gain{
|
||||
{8 * 8, 35 * 8},
|
||||
2,
|
||||
{0, 47},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
NumberField field_amp{
|
||||
{13 * 8, 35 * 8},
|
||||
1,
|
||||
{0, 1},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_transmit{
|
||||
{148, 216, 80, 80},
|
||||
LanguageHelper::currentMessages[LANG_START]};
|
||||
|
||||
MessageHandlerRegistration message_handler_retune{
|
||||
Message::ID::Retune,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const RetuneMessage*>(p);
|
||||
this->on_retune(message->freq, message->range);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::hopper
|
||||
+155
-1
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -178,7 +180,8 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
|
||||
&label_waveform,
|
||||
&waveform,
|
||||
&button_open,
|
||||
&button_save});
|
||||
&button_save,
|
||||
&button_bug_key});
|
||||
|
||||
// Initialize default values for controls
|
||||
field_symbol_rate.set_value(100);
|
||||
@@ -267,6 +270,16 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
|
||||
});
|
||||
};
|
||||
|
||||
button_bug_key.on_select = [&](Button&) {
|
||||
auto bug_key_input_view = nav_.push<OOKEditorBugKeyView>(ook_data.payload);
|
||||
|
||||
bug_key_input_view->on_save = [this](std::string p) {
|
||||
ook_data.payload = p;
|
||||
text_payload.set(ook_data.payload);
|
||||
draw_waveform();
|
||||
};
|
||||
};
|
||||
|
||||
// Configure button to start or stop the transmission
|
||||
button_send_stop.on_select = [this](Button&) {
|
||||
if (!is_transmitting) {
|
||||
@@ -280,4 +293,145 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
|
||||
// initial waveform drawing (should be a single line)
|
||||
draw_waveform();
|
||||
}
|
||||
|
||||
/*************** bug key view ****************/
|
||||
|
||||
OOKEditorBugKeyView::OOKEditorBugKeyView(NavigationView& nav, std::string payload)
|
||||
: nav_{nav},
|
||||
payload_{payload} {
|
||||
add_children({&labels,
|
||||
&field_primary_step,
|
||||
&field_secondary_step,
|
||||
&console,
|
||||
&button_insert_high_level_long,
|
||||
&button_insert_high_level_short,
|
||||
&button_insert_low_level_long,
|
||||
&button_insert_low_level_short,
|
||||
&button_delete,
|
||||
&button_save});
|
||||
|
||||
button_insert_low_level_short.on_select = [this](Button&) {
|
||||
on_insert(InsertType::LOW_LEVEL_SHORT);
|
||||
};
|
||||
|
||||
button_insert_low_level_long.on_select = [this](Button&) {
|
||||
on_insert(InsertType::LOW_LEVEL_LONG);
|
||||
};
|
||||
|
||||
button_insert_high_level_short.on_select = [this](Button&) {
|
||||
on_insert(InsertType::HIGH_LEVEL_SHORT);
|
||||
};
|
||||
|
||||
button_insert_high_level_long.on_select = [this](Button&) {
|
||||
on_insert(InsertType::HIGH_LEVEL_LONG);
|
||||
};
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
on_delete();
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save) on_save(build_payload());
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
auto update_step_buttons = [this](int32_t value, Button& btnLow, Button& btnHigh) {
|
||||
std::string low_level_btn_str;
|
||||
std::string high_level_btn_str;
|
||||
if (value <= 14) { // the button width allow max 14 chars
|
||||
for (int i = 0; i < value; i++) {
|
||||
low_level_btn_str.push_back('0');
|
||||
high_level_btn_str.push_back('1');
|
||||
}
|
||||
} else {
|
||||
low_level_btn_str = to_string_dec_int(value) + " * \"0\"";
|
||||
high_level_btn_str = to_string_dec_int(value) + " * \"1\"";
|
||||
}
|
||||
btnLow.set_text(" "); // set_dirty broken console. this is work around
|
||||
btnHigh.set_text(" ");
|
||||
btnLow.set_text(low_level_btn_str);
|
||||
btnHigh.set_text(high_level_btn_str);
|
||||
};
|
||||
|
||||
field_primary_step.on_change = [&](int32_t) {
|
||||
update_step_buttons(field_primary_step.value(),
|
||||
button_insert_low_level_short,
|
||||
button_insert_high_level_short);
|
||||
update_console();
|
||||
};
|
||||
|
||||
field_secondary_step.on_change = [&](int32_t) {
|
||||
update_step_buttons(field_secondary_step.value(),
|
||||
button_insert_low_level_long,
|
||||
button_insert_high_level_long);
|
||||
update_console();
|
||||
};
|
||||
|
||||
field_primary_step.set_value(1);
|
||||
field_secondary_step.set_value(2);
|
||||
update_step_buttons(field_primary_step.value(),
|
||||
button_insert_low_level_short,
|
||||
button_insert_high_level_short);
|
||||
update_console();
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::on_insert(InsertType type) {
|
||||
auto promise_length = 0;
|
||||
std::string promose_level = "0";
|
||||
switch (type) {
|
||||
case InsertType::LOW_LEVEL_SHORT:
|
||||
promise_length = field_primary_step.value();
|
||||
promose_level = "0";
|
||||
break;
|
||||
case InsertType::LOW_LEVEL_LONG:
|
||||
promise_length = field_secondary_step.value();
|
||||
promose_level = "0";
|
||||
break;
|
||||
case InsertType::HIGH_LEVEL_SHORT:
|
||||
promise_length = field_primary_step.value();
|
||||
promose_level = "1";
|
||||
break;
|
||||
case InsertType::HIGH_LEVEL_LONG:
|
||||
promise_length = field_secondary_step.value();
|
||||
promose_level = "1";
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < promise_length; i++) {
|
||||
payload_ += promose_level;
|
||||
}
|
||||
|
||||
update_console();
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::on_delete() {
|
||||
// I'm aware that if user inputted like: [long high][long high][short high], this will delete a pile of them
|
||||
// but this doesnt matter because:
|
||||
// 1. they should not do it, high or low shoudl cross each other, don't repeat
|
||||
// 2. don't have too much RAM to trach the input trace
|
||||
if (payload_.length() > 0) {
|
||||
size_t len = payload_.length();
|
||||
char last_char = payload_[len - 1];
|
||||
size_t pos = len - 1;
|
||||
while (pos > 0 && payload_[pos - 1] == last_char) {
|
||||
pos--;
|
||||
}
|
||||
payload_.erase(pos);
|
||||
update_console();
|
||||
}
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::update_console() {
|
||||
console.clear(true);
|
||||
console.write(payload_);
|
||||
}
|
||||
|
||||
std::string OOKEditorBugKeyView::build_payload() {
|
||||
return payload_;
|
||||
}
|
||||
|
||||
void OOKEditorBugKeyView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::ook_editor
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -29,6 +31,8 @@ namespace ui::external_app::ook_editor {
|
||||
#define TRANSMISSION_FREQUENCY_DEFAULT 433920000U // Sets the default transmission frequency (27 MHz).
|
||||
#define WAVEFORM_BUFFER_SIZE 550
|
||||
|
||||
/*****************Editor View ******************/
|
||||
|
||||
class OOKEditorAppView : public View {
|
||||
public:
|
||||
void focus() override;
|
||||
@@ -123,6 +127,7 @@ class OOKEditorAppView : public View {
|
||||
Button button_set{{0, 125, 60, 28}, LanguageHelper::currentMessages[LANG_SET]};
|
||||
Button button_open{{68, 125, 80, 28}, LanguageHelper::currentMessages[LANG_OPEN_FILE]};
|
||||
Button button_save{{154, 125, 80, 28}, LanguageHelper::currentMessages[LANG_SAVE_FILE]};
|
||||
Button button_bug_key{{0, 125 + 28 + 3, screen_width, 28}, "Bug Key"};
|
||||
Button button_send_stop{{80, 273, 80, 32}, LanguageHelper::currentMessages[LANG_SEND]};
|
||||
|
||||
// Progress bar to display transmission progress.
|
||||
@@ -132,6 +137,82 @@ class OOKEditorAppView : public View {
|
||||
Waveform waveform{{0, 208, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
|
||||
};
|
||||
|
||||
/******** bug key input view **********/
|
||||
|
||||
enum InsertType {
|
||||
LOW_LEVEL_SHORT,
|
||||
LOW_LEVEL_LONG,
|
||||
HIGH_LEVEL_SHORT,
|
||||
HIGH_LEVEL_LONG
|
||||
};
|
||||
|
||||
class OOKEditorBugKeyView : public View {
|
||||
public:
|
||||
std::function<void(std::string)> on_save{};
|
||||
|
||||
OOKEditorBugKeyView(NavigationView& nav, std::string payload);
|
||||
|
||||
std::string title() const override { return "Bug.K"; };
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::string payload_ = "";
|
||||
std::string path_ = "";
|
||||
uint32_t delay_{0};
|
||||
std::string delay_str{""}; // needed by text_prompt
|
||||
|
||||
void on_insert(InsertType type);
|
||||
void on_delete();
|
||||
void update_console();
|
||||
std::string build_payload();
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Primary Step", Theme::getInstance()->fg_light->foreground},
|
||||
{{(screen_width / 2), 0 * 16}, "Secondary Step", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
NumberField field_primary_step{
|
||||
{0 * 8, 1 * 16},
|
||||
3,
|
||||
{0, 550},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_secondary_step{
|
||||
{(screen_width / 2), 1 * 16},
|
||||
3,
|
||||
{0, 550},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Console console{
|
||||
{0, 3 * 16, screen_width, screen_height - 10 * 16}};
|
||||
|
||||
Button button_insert_low_level_long{
|
||||
{0 * 8, 13 * 16, screen_width / 2, 2 * 16},
|
||||
"00"};
|
||||
|
||||
Button button_insert_low_level_short{
|
||||
{0 * 8, 15 * 16, screen_width / 2, 2 * 16},
|
||||
"0"};
|
||||
|
||||
Button button_insert_high_level_long{
|
||||
{(screen_width / 2), 13 * 16, screen_width / 2, 2 * 16},
|
||||
"11"};
|
||||
|
||||
Button button_insert_high_level_short{
|
||||
{(screen_width / 2), 15 * 16, screen_width / 2, 2 * 16},
|
||||
"1"};
|
||||
|
||||
Button button_delete{
|
||||
{1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"<Backspace"};
|
||||
|
||||
Button button_save{
|
||||
{1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"Save"};
|
||||
};
|
||||
|
||||
}; // namespace ui::external_app::ook_editor
|
||||
|
||||
#endif /*__UI_OOK_EDITOR_H__*/
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* 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_playlist_editor.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::playlist_editor {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<PlaylistEditorView>();
|
||||
}
|
||||
} // namespace ui::external_app::playlist_editor
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_playlist_editor.application_information"), used)) application_information_t _application_information_playlist_editor = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::playlist_editor::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "PlaylistEdit",
|
||||
/*.bitmap_data = */ {
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0F,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0xC3,
|
||||
0x00,
|
||||
0xE0,
|
||||
0xFF,
|
||||
0xEF,
|
||||
0xFF,
|
||||
0xC0,
|
||||
0x00,
|
||||
0x83,
|
||||
0x01,
|
||||
0x00,
|
||||
0x01,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
|
||||
},
|
||||
/*.icon_color = */ ui::Color::cyan().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* 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_playlist_editor.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
#include "file.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "file_reader.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::playlist_editor {
|
||||
|
||||
/*********menu**********/
|
||||
PlaylistEditorView::PlaylistEditorView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
portapack::async_tx_enabled = true;
|
||||
add_children({&labels,
|
||||
&button_new,
|
||||
&text_current_ppl_file,
|
||||
&menu_view,
|
||||
&text_hint,
|
||||
&button_open_playlist,
|
||||
&button_edit,
|
||||
&button_insert,
|
||||
&button_save_playlist});
|
||||
|
||||
menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
text_hint.set("Edit:" +
|
||||
playlist[menu_view.highlighted_index()].substr(playlist[menu_view.highlighted_index()].find_last_of('/') + 1,
|
||||
playlist[menu_view.highlighted_index()].find(',') -
|
||||
playlist[menu_view.highlighted_index()].find_last_of('/') - 1));
|
||||
};
|
||||
|
||||
button_new.on_select = [this](Button&) {
|
||||
if (on_create_ppl()) {
|
||||
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
|
||||
refresh_interface();
|
||||
}
|
||||
};
|
||||
|
||||
button_open_playlist.on_select = [this](Button&) {
|
||||
open_file();
|
||||
};
|
||||
|
||||
button_edit.on_select = [this](Button&) {
|
||||
on_edit_item();
|
||||
};
|
||||
|
||||
button_insert.on_select = [this](Button&) {
|
||||
on_insert_item();
|
||||
};
|
||||
|
||||
button_save_playlist.on_select = [this](Button&) {
|
||||
save_ppl();
|
||||
};
|
||||
|
||||
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON);
|
||||
}
|
||||
|
||||
void PlaylistEditorView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
void PlaylistEditorView::open_file() {
|
||||
auto open_view = nav_.push<FileLoadView>(".PPL");
|
||||
open_view->push_dir(playlist_dir);
|
||||
open_view->on_changed = [this](fs::path new_file_path) {
|
||||
current_ppl_path = new_file_path;
|
||||
on_file_changed(new_file_path);
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistEditorView::swap_opened_file_or_new_button(DisplayFilenameOrNewButton d) {
|
||||
if (d == DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON) {
|
||||
button_new.hidden(false);
|
||||
text_current_ppl_file.hidden(true);
|
||||
} else {
|
||||
button_new.hidden(true);
|
||||
text_current_ppl_file.hidden(false);
|
||||
text_current_ppl_file.set(current_ppl_name_buffer);
|
||||
}
|
||||
refresh_interface();
|
||||
}
|
||||
|
||||
/*
|
||||
NB: same name would became as "open file"
|
||||
*/
|
||||
bool PlaylistEditorView::on_create_ppl() {
|
||||
bool success = false;
|
||||
text_prompt(
|
||||
nav_,
|
||||
current_ppl_name_buffer,
|
||||
100,
|
||||
[&](std::string& s) {
|
||||
current_ppl_name_buffer = s;
|
||||
|
||||
success = true;
|
||||
current_ppl_name_buffer += ".PPL";
|
||||
current_ppl_path = playlist_dir / std::filesystem::path(current_ppl_name_buffer);
|
||||
|
||||
File f;
|
||||
f.open(current_ppl_path, true, true); // prob safer here as standalone obj as read only and then open again in process func
|
||||
f.close();
|
||||
on_file_changed(current_ppl_path);
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void PlaylistEditorView::on_file_changed(const fs::path& new_file_path) {
|
||||
File playlist_file;
|
||||
auto error = playlist_file.open(new_file_path.string());
|
||||
|
||||
if (error) return;
|
||||
|
||||
menu_view.clear();
|
||||
auto reader = FileLineReader(playlist_file);
|
||||
|
||||
for (const auto& line : reader) {
|
||||
playlist.push_back(line);
|
||||
}
|
||||
|
||||
for (auto& line : playlist) {
|
||||
// remove empty lines
|
||||
if (line == "\n" || line == "\r\n" || line == "\r") {
|
||||
playlist.erase(std::remove(playlist.begin(), playlist.end(), line), playlist.end());
|
||||
}
|
||||
|
||||
// remove line end \n etc
|
||||
if (line.length() > 0 && (line[line.length() - 1] == '\n' || line[line.length() - 1] == '\r')) {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
}
|
||||
}
|
||||
text_hint.set("Highlight an entry");
|
||||
|
||||
text_current_ppl_file.set(new_file_path.string());
|
||||
|
||||
ever_opened = true;
|
||||
|
||||
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
|
||||
|
||||
refresh_menu_view();
|
||||
}
|
||||
|
||||
void PlaylistEditorView::refresh_menu_view() {
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& line : playlist) {
|
||||
if (line.length() == 0 || line[0] == '#') {
|
||||
menu_view.add_item({line,
|
||||
ui::Color::grey(),
|
||||
&bitmap_icon_notepad,
|
||||
[this](KeyEvent) {
|
||||
button_insert.focus();
|
||||
}});
|
||||
} else {
|
||||
const auto filename = line.substr(line.find_last_of('/') + 1, line.find(',') - line.find_last_of('/') - 1);
|
||||
menu_view.add_item({filename,
|
||||
ui::Color::white(),
|
||||
&bitmap_icon_cwgen,
|
||||
[this](KeyEvent) {
|
||||
button_edit.focus();
|
||||
}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistEditorView::on_edit_item() {
|
||||
if (!ever_opened || playlist.empty()) {
|
||||
nav_.display_modal("Err", "No entry");
|
||||
return;
|
||||
}
|
||||
auto edit_view = nav_.push<PlaylistItemEditView>(
|
||||
playlist[menu_view.highlighted_index()]);
|
||||
|
||||
edit_view->set_on_delete([this]() {
|
||||
playlist.erase(playlist.begin() + menu_view.highlighted_index());
|
||||
refresh_interface();
|
||||
});
|
||||
|
||||
edit_view->on_save = [this](std::string new_item) {
|
||||
playlist[menu_view.highlighted_index()] = new_item;
|
||||
refresh_interface();
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistEditorView::on_insert_item() {
|
||||
// if (current_ppl_path.empty() || current_ppl_path.string().find_first_not_of(" \t\n\r") == std::string::npos) {
|
||||
if (!ever_opened) { // TODO: this is a workaround because the above line is not working and I took one hour and didn't find the issue
|
||||
nav_.display_modal("Err", "No playlist file loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
auto edit_view = nav_.push<PlaylistItemEditView>(
|
||||
"");
|
||||
|
||||
edit_view->on_save = [&](std::string new_item) {
|
||||
if (playlist.empty()) {
|
||||
playlist.push_back(new_item);
|
||||
} else {
|
||||
playlist.insert(playlist.begin() + menu_view.highlighted_index() + 1, new_item);
|
||||
}
|
||||
refresh_interface();
|
||||
};
|
||||
}
|
||||
|
||||
void PlaylistEditorView::refresh_interface() {
|
||||
const auto previous_index = menu_view.highlighted_index();
|
||||
refresh_menu_view();
|
||||
set_dirty();
|
||||
menu_view.set_highlighted(previous_index);
|
||||
}
|
||||
|
||||
void PlaylistEditorView::save_ppl() {
|
||||
if (current_ppl_path.empty()) {
|
||||
nav_.display_modal("Err", "No playlist file loaded");
|
||||
return;
|
||||
} else if (playlist.empty()) {
|
||||
nav_.display_modal("Err", "List is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
File playlist_file;
|
||||
auto error = playlist_file.open(current_ppl_path.string(), false, false);
|
||||
|
||||
if (error) {
|
||||
nav_.display_modal("Err", "open err");
|
||||
return;
|
||||
}
|
||||
|
||||
// clear file
|
||||
playlist_file.seek(0);
|
||||
playlist_file.truncate();
|
||||
|
||||
// write new data
|
||||
for (const auto& entry : playlist) {
|
||||
playlist_file.write_line(entry);
|
||||
}
|
||||
|
||||
nav_.display_modal("Save", "Saved playlist\n" + current_ppl_path.string());
|
||||
}
|
||||
|
||||
/*********edit**********/
|
||||
|
||||
PlaylistItemEditView::PlaylistItemEditView(
|
||||
NavigationView& nav,
|
||||
std::string item)
|
||||
: nav_{nav},
|
||||
original_item_{item} {
|
||||
add_children({&labels,
|
||||
&field_path,
|
||||
&field_delay,
|
||||
&button_browse,
|
||||
&button_input_delay,
|
||||
&button_delete,
|
||||
&button_save});
|
||||
|
||||
button_browse.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".C16");
|
||||
open_view->push_dir(captures_dir);
|
||||
open_view->on_changed = [this](fs::path path) {
|
||||
field_path.set_text(path.string());
|
||||
path_ = path.string();
|
||||
};
|
||||
field_delay.on_change = [&](auto) {
|
||||
delay_ = field_delay.value();
|
||||
};
|
||||
};
|
||||
|
||||
button_input_delay.on_select = [this](Button&) {
|
||||
delay_str = to_string_dec_uint(delay_);
|
||||
if (delay_str == "0") {
|
||||
delay_str = "";
|
||||
}
|
||||
text_prompt(
|
||||
nav_,
|
||||
delay_str,
|
||||
100,
|
||||
[&](std::string& s) {
|
||||
delay_ = atoi(s.c_str());
|
||||
field_delay.set_value(delay_);
|
||||
refresh_ui();
|
||||
});
|
||||
};
|
||||
|
||||
button_delete.on_select = [this](Button&) {
|
||||
if (on_delete) on_delete();
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_save.on_select = [&](Button&) {
|
||||
if (path_.empty()) {
|
||||
nav_.display_modal("Err", "Select a file\n or press back to cancel");
|
||||
return;
|
||||
}
|
||||
if (on_save) on_save(build_item());
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
if (!on_delete) {
|
||||
button_delete.hidden(true);
|
||||
}
|
||||
|
||||
parse_item(item);
|
||||
refresh_ui();
|
||||
}
|
||||
|
||||
void PlaylistItemEditView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
void PlaylistItemEditView::refresh_ui() {
|
||||
field_path.set_text(path_);
|
||||
field_delay.set_value(delay_);
|
||||
}
|
||||
|
||||
void PlaylistItemEditView::parse_item(std::string item) {
|
||||
// Parse format: path,delay
|
||||
if (item.empty()) {
|
||||
return;
|
||||
}
|
||||
auto parts = split_string(item, ',');
|
||||
if (parts.size() >= 1) {
|
||||
path_ = std::string{parts[0]};
|
||||
}
|
||||
if (parts.size() >= 2) {
|
||||
delay_ = atoi(std::string{parts[1]}.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string PlaylistItemEditView::build_item() const {
|
||||
const auto v = path_ + "," + to_string_dec_uint(field_delay.value());
|
||||
return path_ + "," + to_string_dec_uint(field_delay.value());
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::playlist_editor
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* copyleft Elliot Alderson from F society
|
||||
* copyleft Darlene Alderson from F society
|
||||
*
|
||||
* 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_PLAYLIST_EDITOR_H__
|
||||
#define __UI_PLAYLIST_EDITOR_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui::external_app::playlist_editor {
|
||||
|
||||
enum DisplayFilenameOrNewButton {
|
||||
DISPLAY_FILENAME,
|
||||
DISPLAY_NEW_BUTTON
|
||||
};
|
||||
|
||||
class PlaylistEditorView : public View {
|
||||
public:
|
||||
PlaylistEditorView(NavigationView& nav);
|
||||
std::string title() const override { return "PPL Edit"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::vector<std::string> playlist = {};
|
||||
fs::path current_ppl_path = "";
|
||||
std::string current_ppl_name_buffer = ""; // this is because text_prompt needs it. TODO: this is so annoying, shoudl refactor that func
|
||||
bool ever_opened = false;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "PPL file:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Button button_new{
|
||||
{(sizeof("PPL file:") + 1) * 8, 0 * 16, 8 * 5, 16},
|
||||
"New"};
|
||||
|
||||
Text text_current_ppl_file{
|
||||
{sizeof("PPL file:") * 8, 0 * 16, screen_width - sizeof("PPL file:") * 8, 16},
|
||||
""};
|
||||
|
||||
MenuView menu_view{};
|
||||
|
||||
Text text_hint{
|
||||
{0, 27 * 8, screen_width, 16},
|
||||
"Open a PPL file"};
|
||||
|
||||
Text text_ppl_name{
|
||||
{0, 27 * 8, screen_width, 16},
|
||||
"Highlight an app"};
|
||||
|
||||
Button button_open_playlist{
|
||||
{0, 29 * 8, screen_width / 2 - 1, 32},
|
||||
"Open PPL"};
|
||||
|
||||
Button button_edit{
|
||||
{screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
|
||||
"Edit Item"};
|
||||
|
||||
Button button_insert{
|
||||
{0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
|
||||
"Ins. After"};
|
||||
|
||||
Button button_save_playlist{
|
||||
{screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
|
||||
"Save PPL"};
|
||||
|
||||
void open_file();
|
||||
void swap_opened_file_or_new_button(DisplayFilenameOrNewButton d);
|
||||
void on_file_changed(const fs::path& path);
|
||||
bool on_create_ppl();
|
||||
void refresh_interface();
|
||||
void on_edit_item();
|
||||
void on_insert_item();
|
||||
void refresh_menu_view();
|
||||
void save_ppl();
|
||||
};
|
||||
|
||||
class PlaylistItemEditView : public View {
|
||||
public:
|
||||
std::function<void(std::string)> on_save{};
|
||||
std::function<void()> on_delete{};
|
||||
|
||||
PlaylistItemEditView(NavigationView& nav, std::string item);
|
||||
|
||||
std::string title() const override { return "Edit Item"; };
|
||||
void focus() override;
|
||||
|
||||
void set_on_delete(std::function<void()> callback) {
|
||||
on_delete = callback;
|
||||
button_delete.hidden(false);
|
||||
}
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::string original_item_ = "";
|
||||
std::string path_ = "";
|
||||
uint32_t delay_{0};
|
||||
std::string delay_str{""}; // needed by text_prompt
|
||||
|
||||
void refresh_ui();
|
||||
void parse_item(std::string item);
|
||||
std::string build_item() const;
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "Path:", Theme::getInstance()->fg_light->foreground},
|
||||
{{2 * 8, 5 * 16}, "Delay(ms):", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
TextField field_path{
|
||||
{0, 2 * 16, screen_width, 16},
|
||||
"empty"};
|
||||
|
||||
NumberField field_delay{
|
||||
{11 * 8, 5 * 16},
|
||||
5,
|
||||
{0, 99999},
|
||||
10,
|
||||
' '};
|
||||
|
||||
Button button_browse{
|
||||
{2 * 8, 8 * 16, 8 * 8, 3 * 16},
|
||||
"Browse"};
|
||||
|
||||
Button button_input_delay{
|
||||
{12 * 8, 8 * 16, sizeof("Input Delay") * 8, 3 * 16},
|
||||
"Input Delay"};
|
||||
|
||||
Button button_delete{
|
||||
{1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"Delete"};
|
||||
|
||||
Button button_save{
|
||||
{1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
|
||||
"Save"};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::playlist_editor
|
||||
|
||||
#endif // __UI_PLAYLIST_EDITOR_H__
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* 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_sd_wipe.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::sd_wipe {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<WipeSDView>();
|
||||
}
|
||||
} // namespace ui::external_app::sd_wipe
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_sd_wipe.application_information"), used)) application_information_t _application_information_sd_wipe = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::sd_wipe::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Wipe SD card",
|
||||
/*.bitmap_data = */ {0xF0, 0x3F, 0x58, 0x35, 0x5C, 0x35, 0xFC, 0x3F, 0xFC, 0x3F, 0xFC, 0x3F, 0x3C, 0x1C, 0xBC, 0xC9, 0xBC, 0xE3, 0x2C, 0x77, 0x5C, 0x3E, 0xAC, 0x1C, 0x5C, 0x3E, 0x2C, 0x77, 0x9C, 0xE3, 0xAC, 0xC1},
|
||||
/*.icon_color = */ ui::Color::red().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
Vendored
+4
-2
@@ -22,7 +22,9 @@
|
||||
|
||||
#include "ui_sd_wipe.hpp"
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::sd_wipe {
|
||||
|
||||
Thread* WipeSDView::thread{nullptr};
|
||||
|
||||
@@ -63,4 +65,4 @@ void WipeSDView::focus() {
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::sd_wipe
|
||||
Vendored
+4
-2
@@ -30,7 +30,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::sd_wipe {
|
||||
|
||||
class WipeSDView : public View {
|
||||
public:
|
||||
@@ -87,6 +89,6 @@ class WipeSDView : public View {
|
||||
""};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::sd_wipe
|
||||
|
||||
#endif /*__UI_SD_WIPE_H__*/
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* 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_view_wav.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::view_wav {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<ViewWavView>();
|
||||
}
|
||||
} // namespace ui::external_app::view_wav
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_view_wav.application_information"), used)) application_information_t _application_information_view_wav = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::view_wav::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "WAV Viewer",
|
||||
/*.bitmap_data = */ {0xF0, 0x0F, 0x1C, 0x18, 0x17, 0x38, 0x15, 0x78, 0x15, 0xF8, 0x15, 0x82, 0x15, 0x8B, 0xD5, 0x83, 0xD5, 0xBB, 0xD5, 0x83, 0x15, 0x8B, 0x15, 0x92, 0x15, 0xA0, 0x17, 0x80, 0x1C, 0x80, 0xF0, 0xFF},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'T', 'X'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
Vendored
+3
-2
@@ -28,8 +28,9 @@
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::view_wav {
|
||||
|
||||
void ViewWavView::update_scale(int32_t new_scale) {
|
||||
scale = new_scale;
|
||||
@@ -345,4 +346,4 @@ ViewWavView::~ViewWavView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::view_wav */
|
||||
Vendored
+4
-2
@@ -28,7 +28,9 @@
|
||||
#include "ui_receiver.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
|
||||
namespace ui {
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::view_wav {
|
||||
|
||||
class ViewWavView : public View {
|
||||
public:
|
||||
@@ -202,4 +204,4 @@ class ViewWavView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::view_wav */
|
||||
@@ -50,6 +50,10 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @param read_only: open in readonly mode
|
||||
* @param create: create if it doesnt exist
|
||||
*/
|
||||
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only, bool create) {
|
||||
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
|
||||
if (create)
|
||||
|
||||
@@ -49,3 +49,5 @@ const std::filesystem::path sstv_dir = u"SSTV";
|
||||
const std::filesystem::path wav_dir = u"WAV";
|
||||
const std::filesystem::path whipcalc_dir = u"WHIPCALC";
|
||||
const std::filesystem::path ook_editor_dir = u"OOKFILES";
|
||||
const std::filesystem::path hopper_dir = u"HOPPER";
|
||||
const std::filesystem::path subghz_dir = u"SUBGHZ";
|
||||
|
||||
@@ -51,5 +51,7 @@ extern const std::filesystem::path sstv_dir;
|
||||
extern const std::filesystem::path wav_dir;
|
||||
extern const std::filesystem::path whipcalc_dir;
|
||||
extern const std::filesystem::path ook_editor_dir;
|
||||
extern const std::filesystem::path hopper_dir;
|
||||
extern const std::filesystem::path subghz_dir;
|
||||
|
||||
#endif /* __FILE_PATH_H__ */
|
||||
|
||||
@@ -66,6 +66,11 @@ int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
|
||||
if (direction == prev_direction) {
|
||||
if ((sensitivity_map[portapack::persistent_memory::encoder_dial_sensitivity()] & (1 << state)) == 0)
|
||||
return 0;
|
||||
|
||||
// false: normal, true: reverse
|
||||
if (portapack::persistent_memory::encoder_dial_direction())
|
||||
direction = -direction;
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,12 +110,12 @@ void BtnGridView::set_arrow_up_enabled(bool enabled) {
|
||||
if (enabled) {
|
||||
if (!arrow_up_enabled) {
|
||||
arrow_up_enabled = true;
|
||||
button_pgup.set_text("PAGE UP");
|
||||
button_pgup.set_text("< PREV");
|
||||
}
|
||||
} else if (!enabled) {
|
||||
if (arrow_up_enabled) {
|
||||
arrow_up_enabled = false;
|
||||
button_pgup.set_text(" ");
|
||||
button_pgup.set_text(" ");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -126,12 +126,12 @@ void BtnGridView::set_arrow_down_enabled(bool enabled) {
|
||||
if (enabled) {
|
||||
if (!arrow_down_enabled) {
|
||||
arrow_down_enabled = true;
|
||||
button_pgdown.set_text("PAGE DOWN");
|
||||
button_pgdown.set_text("NEXT >");
|
||||
}
|
||||
} else if (!enabled) {
|
||||
if (arrow_down_enabled) {
|
||||
arrow_down_enabled = false;
|
||||
button_pgdown.set_text(" ");
|
||||
button_pgdown.set_text(" ");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -228,6 +228,10 @@ NewButton* BtnGridView::item_view(size_t index) const {
|
||||
|
||||
void BtnGridView::show_arrows_enabled(bool enabled) {
|
||||
show_arrows = enabled;
|
||||
if (!enabled) {
|
||||
remove_child(&button_pgup);
|
||||
remove_child(&button_pgdown);
|
||||
}
|
||||
}
|
||||
|
||||
bool BtnGridView::set_highlighted(int32_t new_value) {
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
#include "ui_recon.hpp"
|
||||
#include "ui_scanner.hpp"
|
||||
#include "ui_sd_over_usb.hpp"
|
||||
#include "ui_sd_wipe.hpp"
|
||||
#include "ui_search.hpp"
|
||||
#include "ui_settings.hpp"
|
||||
#include "ui_siggen.hpp"
|
||||
@@ -63,10 +62,8 @@
|
||||
// #include "ui_test.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_touchtunes.hpp"
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_weatherstation.hpp"
|
||||
#include "ui_subghzd.hpp"
|
||||
#include "ui_whipcalc.hpp"
|
||||
#include "ui_battinfo.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
@@ -125,7 +122,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
|
||||
{"scanner", "Scanner", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
|
||||
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
@@ -142,7 +139,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"level", "Level", RX, Color::green(), &bitmap_icon_options_radio, new ViewFactory<LevelView>()},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"scanner", "Scanner", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
@@ -153,7 +150,6 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
//{"sstv", "SSTV", RX, Color::dark_grey(), &bitmap_icon_sstv, new ViewFactory<NotImplementedView>()},
|
||||
//{"tetra", "TETRA", RX, Color::dark_grey(), &bitmap_icon_tetra, new ViewFactory<NotImplementedView>()},
|
||||
/* TX ********************************************************************/
|
||||
//{"adsbtx", "ADS-B TX", TX, ui::Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBTxView>()},
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
|
||||
{"bht", "BHT Xy/EP", TX, ui::Color::green(), &bitmap_icon_bht, new ViewFactory<BHTView>()},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
|
||||
@@ -163,19 +159,15 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"antennalength", "Antenna Length", UTILITIES, Color::green(), &bitmap_icon_tools_antenna, new ViewFactory<WhipCalcView>()},
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
|
||||
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
|
||||
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
|
||||
{nullptr, "Wipe SD card", UTILITIES, Color::red(), &bitmap_icon_tools_wipesd, new ViewFactory<WipeSDView>()},
|
||||
};
|
||||
|
||||
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
|
||||
@@ -359,12 +351,29 @@ void SystemStatusView::on_battery_data(const BatteryStateMessage* msg) {
|
||||
batt_was_inited = true;
|
||||
refresh();
|
||||
}
|
||||
|
||||
// Check if charging state changed to charging
|
||||
static bool was_charging = false;
|
||||
if (msg->on_charger && !was_charging) {
|
||||
// Only show charging modal when transitioning to charging state
|
||||
nav_.display_modal(
|
||||
"CHARGING",
|
||||
"Screen on while charging?",
|
||||
YESNO,
|
||||
[this](bool keep_screen_on) {
|
||||
if (!keep_screen_on) {
|
||||
EventDispatcher::set_display_sleep(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
was_charging = msg->on_charger;
|
||||
|
||||
if (!pmem::ui_hide_numeric_battery()) {
|
||||
battery_text.set_battery(msg->valid_mask, msg->percent, msg->on_charger);
|
||||
}
|
||||
if (!pmem::ui_hide_battery_icon()) {
|
||||
battery_icon.set_battery(msg->valid_mask, msg->percent, msg->on_charger);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStatusView::refresh() {
|
||||
|
||||
@@ -38,28 +38,67 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
} else
|
||||
sample_count--;
|
||||
|
||||
if (tone_shape == 0) {
|
||||
if (modulation == 0) {
|
||||
// CW
|
||||
re = 127; // max. signed 8 bits value . (-128 ...+127), max. amplitude , static phasor at 0º
|
||||
im = 0;
|
||||
} else {
|
||||
if (tone_shape == 1) {
|
||||
} else if (modulation == 2) {
|
||||
// Digital BPSK consecutive 0,1,0,...continuous cycle, 1 bit/symbol, at rate of 2 symbols / Freq Tone Periode... without any Pulse shape at the moment .
|
||||
re = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128; // Sending 2 bits by Periode T of the GUI tone, alternative static phasor to 0, -180º , 0º
|
||||
im = 0;
|
||||
tone_phase += tone_delta; // In BPSK-QSPK we are using to calculate each 1/4 of the periode.
|
||||
} else if (modulation == 3) {
|
||||
// Digital QPSK consecutive 00, 01, 10, 11,00, ...continuous cycle ,2 bits/symbol, at rate of 4 symbols / Freq Tone Periode. not random., without any Pulse shape at the moment .
|
||||
|
||||
switch (((tone_phase & 0xFF000000) >> 24)) {
|
||||
case 0 ... 63: // equivalent to 1/4 of total 360º degrees.
|
||||
/* "00" */
|
||||
re = (sine_table_i8[32]); // we are sending symbol-phasor 45º during 1/4 of the total periode
|
||||
im = (sine_table_i8[32 + 64]); // 32 index = rounded (45º/360º * 255 total sin table steps) = 31,875
|
||||
break;
|
||||
|
||||
case 64 ... 127:
|
||||
/* "01" */
|
||||
re = (sine_table_i8[96]); // symbol-phasor 135º
|
||||
im = (sine_table_i8[96 + 64]); // 96 index = 32 + 256/4
|
||||
break;
|
||||
break;
|
||||
|
||||
case 128 ... 191:
|
||||
/* "10" */
|
||||
re = (sine_table_i8[159]); // symbol-phasor 225º
|
||||
im = (sine_table_i8[159 + 64]); // 159 rounded index = 96 + 256/4 = 159.3
|
||||
break;
|
||||
|
||||
case 192 ... 255:
|
||||
/* "11" */
|
||||
re = (sine_table_i8[223]); // symbol-phasor 315º ; 223 rounded index = (315/360) * 255 =223.125
|
||||
im = (sine_table_i8[((223 + 64) & 0xFF)]); // Max index 255, circular periodic conversion.
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
tone_phase += tone_delta; // In BPSK-QSPK we are using to calculate each 1/4 of the periode.
|
||||
|
||||
} else { // Other modulations: FM, DSB, AM
|
||||
if (tone_shape == 0) {
|
||||
// Sine
|
||||
sample = (sine_table_i8[(tone_phase & 0xFF000000) >> 24]);
|
||||
} else if (tone_shape == 2) {
|
||||
} else if (tone_shape == 1) {
|
||||
// Triangle
|
||||
int8_t a = (tone_phase & 0xFF000000) >> 24;
|
||||
sample = (a & 0x80) ? ((a << 1) ^ 0xFF) - 0x80 : (a << 1) + 0x80;
|
||||
} else if (tone_shape == 3) {
|
||||
} else if (tone_shape == 2) {
|
||||
// Saw up
|
||||
sample = ((tone_phase & 0xFF000000) >> 24);
|
||||
} else if (tone_shape == 4) {
|
||||
} else if (tone_shape == 3) {
|
||||
// Saw down
|
||||
sample = ((tone_phase & 0xFF000000) >> 24) ^ 0xFF;
|
||||
} else if (tone_shape == 5) {
|
||||
} else if (tone_shape == 4) {
|
||||
// Square
|
||||
sample = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128;
|
||||
} else if (tone_shape == 6) {
|
||||
} else if (tone_shape == 5) {
|
||||
// Noise generator, pseudo random noise generator, 16 bits linear-feedback shift register (LFSR) algorithm, variant Fibonacci.
|
||||
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register
|
||||
// 16 bits LFSR .taps: 16, 15, 13, 4 ;feedback polynomial: x^16 + x^15 + x^13 + x^4 + 1
|
||||
@@ -79,49 +118,13 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (counter == 15) {
|
||||
counter = 0;
|
||||
}
|
||||
} else if (tone_shape == 7) {
|
||||
// Digital BPSK consecutive 0,1,0,...continuous cycle, 1 bit/symbol, at rate of 2 symbols / Freq Tone Periode... without any Pulse shape at the moment .
|
||||
re = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128; // Sending 2 bits by Periode T of the GUI tone, alternative static phasor to 0, -180º , 0º
|
||||
im = 0;
|
||||
} else if (tone_shape == 8) {
|
||||
// Digital QPSK consecutive 00, 01, 10, 11,00, ...continuous cycle ,2 bits/symbol, at rate of 4 symbols / Freq Tone Periode. not random., without any Pulse shape at the moment .
|
||||
|
||||
switch (((tone_phase & 0xFF000000) >> 24)) {
|
||||
case 0 ... 63: // equivalent to 1/4 of total 360º degrees.
|
||||
/* "00" */
|
||||
re = (sine_table_i8[32]); // we are sending symbol-phasor 45º during 1/4 of the total periode
|
||||
im = (sine_table_i8[32 + 64]); // 32 index = rounded (45º/360º * 255 total sin table steps) = 31,875
|
||||
break;
|
||||
|
||||
case 64 ... 127:
|
||||
/* "01" */
|
||||
re = (sine_table_i8[96]); // symbol-phasor 135º
|
||||
im = (sine_table_i8[96 + 64]); // 96 index = 32 + 256/4
|
||||
break;
|
||||
break;
|
||||
|
||||
case 128 ... 191:
|
||||
/* "10" */
|
||||
re = (sine_table_i8[159]); // symbol-phasor 225º
|
||||
im = (sine_table_i8[159 + 64]); // 159 rounded index = 96 + 256/4 = 159.3
|
||||
break;
|
||||
|
||||
case 192 ... 255:
|
||||
/* "11" */
|
||||
re = (sine_table_i8[223]); // symbol-phasor 315º ; 223 rounded index = (315/360) * 255 =223.125
|
||||
im = (sine_table_i8[((223 + 64) & 0xFF)]); // Max index 255, circular periodic conversion.
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tone_shape != 6) { //(all except Pseudo Random White Noise). We are in (1):periodic signals or (2):BPSK/QPSK , in both cases ,we need Tone updated acum sum phases to modulate in FM / or control phasor phase (BPSK & QPSK.)
|
||||
tone_phase += tone_delta; // In periodic signals(Sine/triangle/square) we are using to FM mod. in BPSK-QSPK we are using to calculate each 1/4 of the periode.
|
||||
if (tone_shape != 5) { // All periodic except Pseudo Random White Noise.
|
||||
tone_phase += tone_delta; // In periodic signals we are using phase to generate the tone to be modulated.
|
||||
}
|
||||
|
||||
if (tone_shape < 7) { // All Option shape signals except BPSK(7) & QPSK(8) we are modulating in FM. (Those two has phase shift modulation XPSK , not FM )
|
||||
if (modulation == 1) {
|
||||
// Do FM modulation
|
||||
delta = sample * fm_delta;
|
||||
|
||||
@@ -130,6 +133,21 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
re = (sine_table_i8[(sphase & 0xFF000000) >> 24]); // sin LUT is not dealing with decimals , output range [-128 ,...127]
|
||||
im = (sine_table_i8[(phase & 0xFF000000) >> 24]);
|
||||
|
||||
} else if (modulation == 4) {
|
||||
// Do Double Side Band modulation
|
||||
re = sample;
|
||||
im = 0;
|
||||
|
||||
} else if (modulation == 5) {
|
||||
// Do AM modulation (100% mod index)
|
||||
re = (127 >> 1) + (sample >> 1);
|
||||
im = 0;
|
||||
|
||||
} else if (modulation == 6) {
|
||||
// Do AM modulation (50% mod index)
|
||||
re = 95 + (sample >> 2);
|
||||
im = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +172,8 @@ void SigGenProcessor::on_message(const Message* const msg) {
|
||||
auto_off = false;
|
||||
|
||||
fm_delta = message.bw * (0xFFFFFFULL / 1536000);
|
||||
tone_shape = message.shape;
|
||||
tone_shape = message.shape & 0xF;
|
||||
modulation = (message.shape & 0xF0) >> 4;
|
||||
|
||||
// lfsr = seed_value ; // Finally not used , init lfsr 8 bits.
|
||||
lfsr_16 = seed_value_16; // init lfsr 16 bits.
|
||||
|
||||
@@ -36,7 +36,7 @@ class SigGenProcessor : public BasebandProcessor {
|
||||
bool configured{false};
|
||||
|
||||
uint32_t tone_delta{0}, fm_delta{}, tone_phase{0};
|
||||
uint8_t tone_shape{};
|
||||
uint8_t tone_shape{}, modulation{};
|
||||
uint32_t sample_count{0};
|
||||
bool auto_off{};
|
||||
int32_t phase{0}, sphase{0}, delta{0}; // they may have sign in the pseudo random sample generation.
|
||||
|
||||
@@ -1060,6 +1060,7 @@ DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluste
|
||||
}
|
||||
}
|
||||
/* go to default */
|
||||
__attribute__ ((fallthrough));
|
||||
#endif
|
||||
default:
|
||||
val = 1; /* Internal error */
|
||||
|
||||
@@ -212,7 +212,7 @@ struct data_t {
|
||||
bool updown_frequency_rx_correction;
|
||||
bool updown_frequency_tx_correction;
|
||||
bool lcd_inverted_mode : 1;
|
||||
bool UNUSED_5 : 1;
|
||||
bool encoder_dial_direction : 1; // false = normal, true = reverse
|
||||
bool UNUSED_6 : 1;
|
||||
bool UNUSED_7 : 1;
|
||||
|
||||
@@ -290,7 +290,7 @@ struct data_t {
|
||||
updown_frequency_rx_correction(false),
|
||||
updown_frequency_tx_correction(false),
|
||||
lcd_inverted_mode(false),
|
||||
UNUSED_5(false),
|
||||
encoder_dial_direction(false),
|
||||
UNUSED_6(false),
|
||||
UNUSED_7(false),
|
||||
|
||||
@@ -417,7 +417,8 @@ void defaults() {
|
||||
set_config_splash(true);
|
||||
set_config_disable_external_tcxo(false);
|
||||
set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL);
|
||||
set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2)
|
||||
set_encoder_dial_direction(false); // false = normal, true = reverse
|
||||
set_config_speaker_disable(true); // Disable AK4951 speaker by default (in case of OpenSourceSDRLab H2)
|
||||
set_menu_color(Color::grey());
|
||||
set_ui_hide_numeric_battery(true); // hide the numeric battery by default - no space to display it
|
||||
|
||||
@@ -1080,6 +1081,13 @@ void set_encoder_rate_multiplier(uint8_t v) {
|
||||
data->encoder_rate_multiplier = v;
|
||||
}
|
||||
|
||||
bool encoder_dial_direction() {
|
||||
return data->encoder_dial_direction;
|
||||
}
|
||||
void set_encoder_dial_direction(bool v) {
|
||||
data->encoder_dial_direction = v;
|
||||
}
|
||||
|
||||
// Recovery mode magic value storage
|
||||
static data_t* data_direct_access = reinterpret_cast<data_t*>(memory::map::backup_ram.base());
|
||||
|
||||
@@ -1246,6 +1254,7 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(data->frequency_tx_correction));
|
||||
pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(data->encoder_dial_sensitivity));
|
||||
pmem_dump_file.write_line("encoder_rate_multiplier: " + to_string_dec_uint(data->encoder_rate_multiplier));
|
||||
pmem_dump_file.write_line("encoder_dial_direction: " + to_string_dec_uint(data->encoder_dial_direction)); // 0 = normal, 1 = reverse
|
||||
pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb));
|
||||
pmem_dump_file.write_line("config_mode_storage: 0x" + to_string_hex(data->config_mode_storage, 8));
|
||||
pmem_dump_file.write_line("dst_config: 0x" + to_string_hex((uint32_t)data->dst_config.v, 8));
|
||||
|
||||
@@ -252,6 +252,8 @@ uint8_t encoder_dial_sensitivity();
|
||||
void set_encoder_dial_sensitivity(uint8_t v);
|
||||
uint8_t encoder_rate_multiplier();
|
||||
void set_encoder_rate_multiplier(uint8_t v);
|
||||
bool encoder_dial_direction();
|
||||
void set_encoder_dial_direction(bool v);
|
||||
|
||||
uint32_t config_mode_storage_direct();
|
||||
void set_config_mode_storage_direct(uint32_t v);
|
||||
|
||||
@@ -35,6 +35,13 @@ struct JammerChannel {
|
||||
uint32_t duration;
|
||||
};
|
||||
|
||||
struct HopperChannel {
|
||||
bool enabled;
|
||||
uint64_t center;
|
||||
uint32_t width;
|
||||
uint32_t duration;
|
||||
};
|
||||
|
||||
struct ToneDef {
|
||||
uint32_t delta;
|
||||
uint32_t duration;
|
||||
@@ -61,7 +68,10 @@ struct SharedMemory {
|
||||
|
||||
union {
|
||||
ToneData tones_data;
|
||||
JammerChannel jammer_channels[24];
|
||||
struct {
|
||||
JammerChannel jammer_channels[24];
|
||||
HopperChannel hopper_channels[24];
|
||||
} dummy_seperate;
|
||||
uint8_t data[512];
|
||||
} bb_data{{{{0, 0}}, 0, {0}}};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(standalone_apps)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
for fname in $(ls ../graphics/); do
|
||||
fname=$(echo "$fname" | awk -F'.' '{print $1}')
|
||||
out=$(grep -r --exclude="bitmap.hpp" "$fname" ../* )
|
||||
ret=$?
|
||||
if [[ "$ret" -ne 0 ]]; then
|
||||
echo "$fname not found"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
# Copyleft Whiterose from the Dark Army
|
||||
#
|
||||
# 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.
|
||||
|
||||
# this tool allow you flash pp and copy the apps in fastest way. note that you need to have a sdcard switcher to switch sdcard to pc.
|
||||
# although, if you don't have one, you still can take out the sdcard and plug it into your pc, but i think it would not pretty fast.
|
||||
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import glob
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
# config
|
||||
SUDO_PASSWORD = ""
|
||||
SDCARD_LABEL = "portapack"
|
||||
|
||||
|
||||
def write_command(ser, command, wait_response=None, remove_echo=False, dont_wait_ch=False):
|
||||
|
||||
# clean buffer
|
||||
while True:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
ser.read(ser.in_waiting)
|
||||
else:
|
||||
break
|
||||
except:
|
||||
break
|
||||
|
||||
command_with_newline = command + "\r\n"
|
||||
try:
|
||||
ser.write(command_with_newline.encode())
|
||||
except Exception as e:
|
||||
return f"write err: {str(e)}"
|
||||
|
||||
response = ""
|
||||
expected_echo = f"{command}\r\n"
|
||||
wait_response_met = wait_response is None
|
||||
|
||||
while True:
|
||||
try:
|
||||
if ser.in_waiting:
|
||||
msg = ser.read(ser.in_waiting).decode('utf-8')
|
||||
response += msg
|
||||
|
||||
if wait_response and wait_response in response:
|
||||
wait_response_met = True
|
||||
|
||||
if wait_response_met and (dont_wait_ch or "ch> " in response):
|
||||
if remove_echo:
|
||||
return response.replace(expected_echo, "")
|
||||
return response
|
||||
|
||||
except serial.SerialTimeoutException:
|
||||
continue
|
||||
except Exception as e:
|
||||
return f"e: {str(e)}"
|
||||
|
||||
def get_serial_devices():
|
||||
ports = serial.tools.list_ports.comports()
|
||||
|
||||
if not ports:
|
||||
print("no serial device found")
|
||||
return None
|
||||
|
||||
portapack_device = None
|
||||
for port in ports:
|
||||
if port.product == "PortaPack Mayhem":
|
||||
print(f"found your pp:")
|
||||
print(f"device: {port.device}")
|
||||
print(f"description: {port.description}")
|
||||
print(f"hwid: {port.hwid}")
|
||||
if port.manufacturer:
|
||||
print(f"manufacturer: {port.manufacturer}")
|
||||
if port.serial_number:
|
||||
print(f"serial_number: {port.serial_number}")
|
||||
portapack_device = port
|
||||
break
|
||||
|
||||
if not portapack_device:
|
||||
print("no pp device found")
|
||||
|
||||
return portapack_device
|
||||
|
||||
def run_sudo_command(cmd):
|
||||
|
||||
if not SUDO_PASSWORD:
|
||||
print("sudo password not set, please hard code it in the script")
|
||||
return
|
||||
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
['sudo', '-S'] + cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True
|
||||
)
|
||||
stdout, stderr = process.communicate(input=SUDO_PASSWORD + '\n')
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, cmd, stdout, stderr)
|
||||
return stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"sudo command failed: {e}")
|
||||
raise
|
||||
|
||||
def get_pp_device_linux():
|
||||
try:
|
||||
cmd = ["lsblk", "-o", "NAME,LABEL,FSTYPE,SIZE,MOUNTPOINT", "-n", "-p"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# find your pp sd
|
||||
for line in result.stdout.splitlines():
|
||||
clean_line = line.replace('└─', '').replace('├─', '').strip()
|
||||
parts = clean_line.split()
|
||||
|
||||
if len(parts) >= 2 and SDCARD_LABEL in parts[1]: # checker
|
||||
device_path = parts[0]
|
||||
# if path valid
|
||||
if not os.path.exists(device_path):
|
||||
continue
|
||||
|
||||
# if mounted, return mountpoint
|
||||
if len(parts) >= 5:
|
||||
mountpoint = parts[4]
|
||||
return device_path, mountpoint
|
||||
|
||||
# if not, create temp dir and mount
|
||||
mount_point = tempfile.mkdtemp()
|
||||
print(f"mount {device_path} to {mount_point}")
|
||||
run_sudo_command(["mount", "-o", f"uid={os.getuid()},gid={os.getgid()}", device_path, mount_point])
|
||||
return device_path, mount_point
|
||||
|
||||
return None, None
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"command failed: {str(e)}")
|
||||
return None, None
|
||||
|
||||
def wait_for_pp_drive():
|
||||
while True:
|
||||
if os.name == 'nt': # win didn't test yet, TODO
|
||||
drives = [d + ':' for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if os.path.exists(d + ':')]
|
||||
for drive in drives:
|
||||
if os.path.basename(os.path.abspath(drive)).lower() == SDCARD_LABEL:
|
||||
return drive, None
|
||||
else: # linux and mac
|
||||
device_path, mount_point = get_pp_device_linux()
|
||||
if device_path and mount_point:
|
||||
return mount_point, device_path
|
||||
time.sleep(1)
|
||||
|
||||
def run():
|
||||
# worker 1: check device connected
|
||||
device = get_serial_devices()
|
||||
if not device:
|
||||
print("no pp device found")
|
||||
return
|
||||
|
||||
try:
|
||||
# worker 2: open serial and send hackrf cmd
|
||||
ser = serial.Serial(device.device, baudrate=115200, timeout=1)
|
||||
for i in range(2):
|
||||
write_command(ser, "hackrf")
|
||||
ser.close()
|
||||
|
||||
# worker 3: hint user to swtich sdcard to pc
|
||||
print("\033[92m\n\t+------------------------+")
|
||||
print("\t| switch PP sdcard to PC |")
|
||||
print("\t+------------------------+\033[0m")
|
||||
print("wait PP sdcard...")
|
||||
|
||||
# worker 4: wait pp sd
|
||||
mount_point, device_path = wait_for_pp_drive()
|
||||
if not mount_point:
|
||||
print("no pp sd found")
|
||||
return
|
||||
print(f"find pp sd: {mount_point}")
|
||||
|
||||
# worker 5: copy ppma and ppmp apps
|
||||
apps_dir = os.path.join(mount_point, 'APPS')
|
||||
if not os.path.exists(apps_dir):
|
||||
os.makedirs(apps_dir)
|
||||
|
||||
firmware_dir = os.path.join('.', 'firmware', 'application')
|
||||
for ext in ['ppma', 'ppmp']:
|
||||
for file in glob.glob(os.path.join(firmware_dir, f'*.{ext}')):
|
||||
dest = os.path.join(apps_dir, os.path.basename(file))
|
||||
print(f"cp: {os.path.basename(file)}")
|
||||
shutil.copy2(file, dest)
|
||||
|
||||
print("apps copy done")
|
||||
|
||||
# worker 6: umount pp sd
|
||||
if os.name != 'nt' and device_path:
|
||||
run_sudo_command(["umount", mount_point])
|
||||
os.rmdir(mount_point) # 删除临时挂载点
|
||||
|
||||
# worker 7: run flash cmd with hackrf_spiflash util
|
||||
print("\nflash...")
|
||||
process = subprocess.Popen(
|
||||
["hackrf_spiflash", "-w", "./firmware/portapack-mayhem-firmware.bin"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True
|
||||
)
|
||||
|
||||
while True:
|
||||
output = process.stdout.readline()
|
||||
if output:
|
||||
print(output.strip())
|
||||
if process.poll() is not None:
|
||||
break
|
||||
|
||||
if process.returncode != 0:
|
||||
raise subprocess.CalledProcessError(process.returncode, "hackrf_spiflash")
|
||||
|
||||
# worker 8: hint user to swtich sdcard switcher back to pp
|
||||
print("\033[92m\n\t+------------------------------------------------+")
|
||||
print("\t| switch sdcard switcher back to pp, then reboot |")
|
||||
print("\t+------------------------------------------------+\033[0m")
|
||||
|
||||
except serial.SerialException as e:
|
||||
print(f"serial comm error: {str(e)}")
|
||||
except OSError as e:
|
||||
print(f"file operation error: {str(e)}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"system command error: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"error: {str(e)}")
|
||||
# ensure umount when error
|
||||
if os.name != 'nt' and 'mount_point' in locals() and device_path:
|
||||
try:
|
||||
run_sudo_command(["umount", mount_point])
|
||||
os.rmdir(mount_point)
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
run()
|
||||
except Exception as e:
|
||||
print(f"error: {str(e)}")
|
||||
sys.exit(1)
|
||||
@@ -53,7 +53,7 @@ AboutView::AboutView(NavigationView& nav) {
|
||||
button_ok.focus();
|
||||
};
|
||||
|
||||
for (auto& authors_line : authors_list) {
|
||||
for (auto& authors_line : mayhem_information_list) {
|
||||
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
|
||||
if (authors_line.size() > 0) {
|
||||
if (authors_line[0] == '#') {
|
||||
@@ -84,15 +84,13 @@ void AboutView::on_frame_sync() {
|
||||
menu_view.set_highlighted(current + 1);
|
||||
} else {
|
||||
menu_view.set_highlighted(0);
|
||||
// ^ to go back to the REAL top instead of make the 2 at the top to make the title disappeares
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
menu_view.set_highlighted(2); // the first line that has human name
|
||||
menu_view.set_highlighted(3); // contributors block starting line
|
||||
}
|
||||
|
||||
bool AboutView::on_touch(const TouchEvent) {
|
||||
@@ -141,8 +139,12 @@ def get_contributors(url):
|
||||
|
||||
def generate_content(projects):
|
||||
project_contrib = []
|
||||
project_contrib.append("string_view authors_list[] = {\n")
|
||||
project_contrib.append(" \"# * List of contributors * \",\n")
|
||||
project_contrib.append("string_view mayhem_information_list[] = {\n")
|
||||
project_contrib.append(" \"#****** Mayhem Community ******\",\n")
|
||||
project_contrib.append(" \" \",\n")
|
||||
project_contrib.append(" \" https://discord.mayhem.app\",\n")
|
||||
project_contrib.append(" \" \",\n")
|
||||
project_contrib.append(" \"#**** List of contributors ****\",\n")
|
||||
for project in projects:
|
||||
project_contrib.append(" \" \",\n")
|
||||
project_contrib.append(f" \"#{project[0]}:\",\n")
|
||||
@@ -170,7 +172,7 @@ def pp_create_ui_about_simple_cpp(cpp_file, cppheader, cppcontent, cppfooter):
|
||||
|
||||
def pp_change_ui_about_simple_cpp(cpp_file, cppcontent):
|
||||
content = []
|
||||
content_pattern = re.compile(r"string_view authors_list\[\] = {\n(?:\s+(?:.*,\n)+\s+.*};\n)", re.MULTILINE)
|
||||
content_pattern = re.compile(r"string_view mayhem_information_list\[\] = {\n(?:\s+(?:.*,\n)+\s+.*};\n)", re.MULTILINE)
|
||||
|
||||
# Read original file
|
||||
with open(cpp_file, 'r') as file:
|
||||
|
||||
@@ -161,3 +161,23 @@ write_image(spi_image, output_path)
|
||||
|
||||
percent_remaining = round(1000 * pad_size / spi_size) / 10;
|
||||
print("Space remaining in flash ROM:", pad_size, "bytes (", percent_remaining, "%)")
|
||||
|
||||
|
||||
# copy the fast flash script
|
||||
build_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'build')
|
||||
flash_py_path = os.path.join(build_dir, 'flash.py')
|
||||
|
||||
if not os.path.exists(flash_py_path):
|
||||
|
||||
current_dir = os.path.dirname(__file__)
|
||||
source_file = os.path.join(current_dir, 'fast_flash_pp_and_copy_apps.py')
|
||||
|
||||
if not os.path.exists(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
# cp
|
||||
import shutil
|
||||
print(f"\ncopy {source_file} to {flash_py_path}\n")
|
||||
shutil.copy2(source_file, flash_py_path)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
#
|
||||
# copyleft Elliot Alderson from F society
|
||||
# copyleft Darlene Alderson from F society
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
import re
|
||||
import tkinter as tk
|
||||
from typing import Dict, List, Tuple
|
||||
import argparse
|
||||
from dataclasses import dataclass
|
||||
|
||||
"""
|
||||
TODO: Multile class in one file seperation
|
||||
TODO: Add more widget type
|
||||
|
||||
widget compatible guide:
|
||||
|
||||
1. add the preview color in the "widgets = {" table
|
||||
(note that this color is only for easier to distinguish)
|
||||
|
||||
2. add the widget type and also regex in the "parsers" table
|
||||
(note that your regex should apply all the possible constructor overload (re-impl))
|
||||
|
||||
"""
|
||||
|
||||
# pp hard coded vars
|
||||
screen_width = 240
|
||||
SCREEN_W = 240
|
||||
screen_height = 320
|
||||
CHARACTER_WIDTH = 8
|
||||
LINE_HEIGHT = 16
|
||||
topbar_offset = 16
|
||||
# widgets actually drew after the top bar,
|
||||
# for example if Y = 0 then Y on screen actually is 16
|
||||
|
||||
# scale factor
|
||||
scale = 2
|
||||
|
||||
widgets = {
|
||||
"Button": "lightgray",
|
||||
"NewButton": "lightyellow",
|
||||
"Text": "lightblue",
|
||||
"Rectangle": "lightgreen",
|
||||
"Image": "pink",
|
||||
"ImageButton": "lightpink",
|
||||
"NumberField": "peachpuff",
|
||||
"ProgressBar": "lightcoral",
|
||||
"Console": "wheat",
|
||||
"Checkbox": "plum",
|
||||
"Label": "lavender",
|
||||
"TextField": "paleturquoise",
|
||||
"OptionsField": "palegreen",
|
||||
"VuMeter": "sandybrown",
|
||||
"BigFrequency": "khaki"
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class Widget:
|
||||
name: str
|
||||
widget_type: str
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
text: str = ""
|
||||
|
||||
class WidgetParser:
|
||||
def __init__(self):
|
||||
|
||||
self.parsers: Dict[str, re.Pattern] = {
|
||||
'Button': re.compile( # TODO: fix those that using language helper
|
||||
r'Button\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'NewButton': re.compile(
|
||||
r'NewButton\s+(\w+)\s*\{\s*(?:\{([^}]+)\}|{}),\s*(?:"([^"]*)"|\{\}),\s*(?:[^,}]+(?:,\s*[^}]+)*|\{\})\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'Text': re.compile(
|
||||
r'Text\s+(\w+)\s*\{\s*\{([^}]+)\}(?:\s*,\s*"([^"]*)")?\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'ProgressBar': re.compile(
|
||||
r'ProgressBar\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'Console': re.compile(
|
||||
r'Console\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'Label': re.compile(
|
||||
r'Label\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'VuMeter': re.compile(
|
||||
r'VuMeter\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+,\s*(?:true|false)\s*\};',
|
||||
re.MULTILINE
|
||||
),
|
||||
'BigFrequency': re.compile(
|
||||
r'BigFrequency\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+\s*\};',
|
||||
re.MULTILINE
|
||||
)
|
||||
}
|
||||
|
||||
def parse_coordinates(self, coord_str: str) -> Tuple[int, int, int, int]:
|
||||
"""Parse coordinate string like '2 * 8, 8 * 16, 8 * 8, 3 * 16'"""
|
||||
if not coord_str or coord_str.isspace():
|
||||
return (0, 0, 0, 0) # for empty constructor fallback
|
||||
|
||||
# split and evaluate each coordinate expression
|
||||
coords = [eval(x.strip()) for x in coord_str.split(',')]
|
||||
|
||||
# ensure we have exactly 4 coordinates, pad with zeros if needed
|
||||
while len(coords) < 4:
|
||||
coords.append(0)
|
||||
|
||||
# only take the first 4 coordinates if there are more
|
||||
return tuple(coords[:4])
|
||||
|
||||
def parse_file(self, filepath: str) -> List[Widget]:
|
||||
with open(filepath, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
widgets = []
|
||||
for widget_type, pattern in self.parsers.items():
|
||||
matches = pattern.finditer(content)
|
||||
for match in matches:
|
||||
name = match.group(1) # Widget name is always in group 1
|
||||
coords = self.parse_coordinates(match.group(2) if match.group(2) else "")
|
||||
|
||||
# Only try to get text if the pattern has 3 or more groups
|
||||
text = ""
|
||||
try:
|
||||
if match.lastindex >= 3:
|
||||
text = match.group(3) if match.group(3) else ""
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
widgets.append(Widget(
|
||||
name=name,
|
||||
widget_type=widget_type,
|
||||
x=coords[0],
|
||||
y=coords[1],
|
||||
width=coords[2],
|
||||
height=coords[3],
|
||||
text=text
|
||||
))
|
||||
|
||||
return widgets
|
||||
|
||||
class WidgetPreview(tk.Tk):
|
||||
def __init__(self, widgets: List[Widget]):
|
||||
super().__init__()
|
||||
self.title("Widget Preview")
|
||||
|
||||
self.canvas = tk.Canvas(self, width=screen_width * scale, height=screen_height * scale, bg='white')
|
||||
self.canvas.pack(padx=10, pady=10)
|
||||
|
||||
self.all_text_elements = []
|
||||
self.draw_widgets(widgets)
|
||||
|
||||
def draw_widgets(self, widgets: List[Widget]):
|
||||
draw_top_bar(self)
|
||||
for widget in widgets:
|
||||
self.draw_widget(widget)
|
||||
|
||||
def draw_widget(self, widget: Widget):
|
||||
print(f"Drawing widget: {widget.name} ({widget.widget_type})")
|
||||
|
||||
x1 = widget.x * scale
|
||||
y1 = (widget.y + topbar_offset) * scale
|
||||
x2 = x1 + (widget.width * scale)
|
||||
y2 = y1 + (widget.height * scale)
|
||||
|
||||
print(f"Coordinates: ({x1}, {y1}), ({x2}, {y2})")
|
||||
|
||||
if widget.widget_type == "VuMeter":
|
||||
segment_height = widget.height / 8 * scale
|
||||
for i in range(8):
|
||||
self.canvas.create_rectangle(
|
||||
x1, y1 + (i * segment_height),
|
||||
x2, y1 + ((i+1) * segment_height),
|
||||
fill=widgets[widget.widget_type],
|
||||
outline="gray"
|
||||
)
|
||||
elif widget.widget_type == "BigFrequency":
|
||||
# Draw 7-segment style display
|
||||
self.canvas.create_rectangle(
|
||||
x1, y1, x2, y2,
|
||||
fill=widgets[widget.widget_type],
|
||||
outline="gray"
|
||||
)
|
||||
self.canvas.create_text(
|
||||
(x1 + x2) / 2,
|
||||
(y1 + y2) / 2,
|
||||
text="433.92" # placeholder text
|
||||
)
|
||||
|
||||
else:
|
||||
# defualt rendering
|
||||
rect_id = self.canvas.create_rectangle(
|
||||
x1, y1, x2, y2,
|
||||
fill=widgets[widget.widget_type]
|
||||
)
|
||||
|
||||
type_text_id = self.canvas.create_text(
|
||||
(x1 + x2) // 2,
|
||||
(y1 + y2) // 2,
|
||||
text=widget.widget_type
|
||||
)
|
||||
|
||||
detail_text_id = self.canvas.create_text(
|
||||
(x1 + x2) // 2,
|
||||
(y1 + y2) // 2,
|
||||
text=f"{widget.widget_type}|{widget.name}|{widget.text}",
|
||||
state='hidden'
|
||||
)
|
||||
|
||||
widget_texts = {
|
||||
'type': type_text_id,
|
||||
'detail': detail_text_id
|
||||
}
|
||||
self.all_text_elements.append(widget_texts)
|
||||
|
||||
# hover handlers
|
||||
def on_enter(event):
|
||||
for texts in self.all_text_elements:
|
||||
self.canvas.itemconfig(texts['type'], state='hidden')
|
||||
self.canvas.itemconfig(texts['detail'], state='hidden')
|
||||
self.canvas.itemconfig(detail_text_id, state='normal')
|
||||
self.canvas.tag_raise(detail_text_id)
|
||||
|
||||
def on_leave(event):
|
||||
for texts in self.all_text_elements:
|
||||
self.canvas.itemconfig(texts['type'], state='normal')
|
||||
self.canvas.tag_raise(texts['type'])
|
||||
self.canvas.itemconfig(texts['detail'], state='hidden')
|
||||
|
||||
for item_id in [rect_id, type_text_id, detail_text_id]:
|
||||
self.canvas.tag_bind(item_id, '<Enter>', on_enter)
|
||||
self.canvas.tag_bind(item_id, '<Leave>', on_leave)
|
||||
|
||||
def draw_top_bar(self):
|
||||
self.canvas.create_rectangle(0, 0, screen_width * scale, topbar_offset * scale, fill='lightblue')
|
||||
self.canvas.create_text(screen_width * scale // 2, topbar_offset * scale // 2, text='I\'m Top Bar, hover mouse on items to check details', fill='black')
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Preview UI widgets from hpp files')
|
||||
parser.add_argument('file', help='Path to the hpp file')
|
||||
args = parser.parse_args()
|
||||
|
||||
widget_parser = WidgetParser()
|
||||
widgets = widget_parser.parse_file(args.file)
|
||||
|
||||
app = WidgetPreview(widgets)
|
||||
app.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,11 +0,0 @@
|
||||
f=124700000,m=AM,bw=DSB 6k,d=BNE Approach NW
|
||||
f=123500000,m=AM,bw=DSB 6k,d=BNE Approach 2
|
||||
f=125600000,m=AM,bw=DSB 6k,d=BNE Approach 3
|
||||
f=120500000,m=AM,bw=DSB 6k,d=BNE RWY 19L/01R
|
||||
f=118000000,m=AM,bw=DSB 6k,d=BNE RWY 19R/01L
|
||||
f=113200000,m=AM,bw=DSB 6k,d=BNE ATIS 1
|
||||
f=125500000,m=AM,bw=DSB 6k,d=BNE ATIS 2
|
||||
f=121700000,m=AM,bw=DSB 6k,d=BNE GRND 1
|
||||
f=122250000,m=AM,bw=DSB 6k,d=BNE GRND 2
|
||||
f=124050000,m=AM,bw=DSB 6k,d=BNE GRND 3
|
||||
f=125700000,m=AM,bw=DSB 6k,d=OOL Approach E
|
||||
@@ -1,28 +0,0 @@
|
||||
f=132475000,m=AM,bw=DSB 6k,d=ATIS - Civil
|
||||
f=130550000,m=AM,bw=DSB 6k,d=AbelagUnicom - Civil
|
||||
f=118250000,m=AM,bw=DSB 6k,d=Approach - Civil
|
||||
f=120100000,m=AM,bw=DSB 6k,d=Approach - Civil
|
||||
f=122500000,m=AM,bw=DSB 6k,d=Approach Military Crossing
|
||||
f=121950000,m=AM,bw=DSB 6k,d=Clearance Delivery - Civil
|
||||
f=126625000,m=AM,bw=DSB 6k,d=Departure - Civil
|
||||
f=127575000,m=AM,bw=DSB 6k,d=Final 25L - Civil
|
||||
f=129725000,m=AM,bw=DSB 6k,d=Final 25R - Civil
|
||||
f=118050000,m=AM,bw=DSB 6k,d=Ground - Civil
|
||||
f=121700000,m=AM,bw=DSB 6k,d=Ground - Civil
|
||||
f=121875000,m=AM,bw=DSB 6k,d=Ground - Civil
|
||||
f=118600000,m=AM,bw=DSB 6k,d=Tower VHF - Civil
|
||||
f=120775000,m=AM,bw=DSB 6k,d=Tower - Civil
|
||||
f=127150000,m=AM,bw=DSB 6k,d=Tower Spare - Civil
|
||||
f=240400000,m=AM,bw=DSB 6k,d=Air to Air - Military
|
||||
f=241000000,m=AM,bw=DSB 6k,d=Air to Air - Military
|
||||
f=289400000,m=AM,bw=DSB 6k,d=Military Approach
|
||||
f=362300000,m=AM,bw=DSB 6k,d=Approach Military Crossing - Military
|
||||
f=389300000,m=AM,bw=DSB 6k,d=Approach Military Crossing - Military
|
||||
f=138250000,m=AM,bw=DSB 6k,d=Inflight Safety - Military
|
||||
f=140575000,m=AM,bw=DSB 6k,d=Melsbroek Ops - Military
|
||||
f=367950000,m=AM,bw=DSB 6k,d=Melsbroek Ops - Military
|
||||
f=257800000,m=AM,bw=DSB 6k,d=Tower - Military
|
||||
f=388500000,m=AM,bw=DSB 6k,d=Tower - Military
|
||||
f=388525000,m=AM,bw=DSB 6k,d=Tower - Military
|
||||
f=121725000,m=AM,bw=DSB 6k,d=Wing Ops - Military
|
||||
f=336500000,m=AM,bw=DSB 6k,d=Wing Ops UHF - Military
|
||||
@@ -1,56 +0,0 @@
|
||||
f=87800000,m=WFM,bw=200k,d=France Inter
|
||||
f=87600000,m=WFM,bw=200k,d=France Inter
|
||||
f=88200000,m=WFM,bw=200k,d=Generations
|
||||
f=88600000,m=WFM,bw=200k,d=Radio Soleil
|
||||
f=89000000,m=WFM,bw=200k,d=RFI
|
||||
f=89400000,m=WFM,bw=200k,d=Radio Libertaire
|
||||
f=89900000,m=WFM,bw=200k,d=TSF Jazz
|
||||
f=90400000,m=WFM,bw=200k,d=Nostalgie
|
||||
f=90900000,m=WFM,bw=200k,d=Chante France
|
||||
f=91300000,m=WFM,bw=200k,d=Cherie FM
|
||||
f=91700000,m=WFM,bw=200k,d=France Musique
|
||||
f=92100000,m=WFM,bw=200k,d=Mouv'
|
||||
f=92600000,m=WFM,bw=200k,d=Tropiques FM
|
||||
f=93100000,m=WFM,bw=200k,d=Cause Commune
|
||||
f=93100000,m=WFM,bw=200k,d=Aligre FM
|
||||
f=93500000,m=WFM,bw=200k,d=France Culture
|
||||
f=93900000,m=WFM,bw=200k,d=Vivre FM
|
||||
f=93900000,m=WFM,bw=200k,d=Radio Campus
|
||||
f=94300000,m=WFM,bw=200k,d=Radio Orient
|
||||
f=94800000,m=WFM,bw=200k,d=Radio Shalom
|
||||
f=94800000,m=WFM,bw=200k,d=Radio J
|
||||
f=94800000,m=WFM,bw=200k,d=RCJ
|
||||
f=95200000,m=WFM,bw=200k,d=Ici Et Maintenant
|
||||
f=95200000,m=WFM,bw=200k,d=Neo
|
||||
f=95600000,m=WFM,bw=200k,d=Radio Courtoisie
|
||||
f=96000000,m=WFM,bw=200k,d=Skyrock
|
||||
f=96400000,m=WFM,bw=200k,d=BFM Business
|
||||
f=96900000,m=WFM,bw=200k,d=Voltage
|
||||
f=97400000,m=WFM,bw=200k,d=Rire Et Chansons
|
||||
f=97800000,m=WFM,bw=200k,d=Ado
|
||||
f=98200000,m=WFM,bw=200k,d=Radio FG
|
||||
f=98600000,m=WFM,bw=200k,d=Radio Alfa
|
||||
f=99000000,m=WFM,bw=200k,d=Latina
|
||||
f=99500000,m=WFM,bw=200k,d=AYP FM
|
||||
f=99500000,m=WFM,bw=200k,d=France Maghreb 2
|
||||
f=99900000,m=WFM,bw=200k,d=Sud Radio +
|
||||
f=100300000,m=WFM,bw=200k,d=NRJ
|
||||
f=100700000,m=WFM,bw=200k,d=Frequence Protestante
|
||||
f=100700000,m=WFM,bw=200k,d=Radio Notre-Dame
|
||||
f=101100000,m=WFM,bw=200k,d=Radio Classique
|
||||
f=101500000,m=WFM,bw=200k,d=Radio Nova
|
||||
f=101900000,m=WFM,bw=200k,d=Fun Radio
|
||||
f=102300000,m=WFM,bw=200k,d=Oui FM
|
||||
f=102700000,m=WFM,bw=200k,d=M Radio
|
||||
f=103100000,m=WFM,bw=200k,d=RMC
|
||||
f=103500000,m=WFM,bw=200k,d=Europe 2
|
||||
f=103900000,m=WFM,bw=200k,d=RFM
|
||||
f=104300000,m=WFM,bw=200k,d=RTL
|
||||
f=104700000,m=WFM,bw=200k,d=Europe 1
|
||||
f=105100000,m=WFM,bw=200k,d=FIP
|
||||
f=105500000,m=WFM,bw=200k,d=France Info
|
||||
f=105900000,m=WFM,bw=200k,d=RTL2
|
||||
f=106300000,m=WFM,bw=200k,d=Frequence Paris Plurielle
|
||||
f=106700000,m=WFM,bw=200k,d=Beur FM
|
||||
f=107100000,m=WFM,bw=200k,d=France Bleu
|
||||
f=107500000,m=WFM,bw=200k,d=Africa Radio
|
||||
@@ -1,4 +0,0 @@
|
||||
f=92500000,m=WFM,bw=200k,d=NRK Oslo
|
||||
f=94800000,m=WFM,bw=200k,d=NRK Bergen
|
||||
f=97200000,m=WFM,bw=200k,d=NRK Melhus
|
||||
f=98600000,m=WFM,bw=200k,d=NRK Arendal
|
||||
@@ -1,58 +0,0 @@
|
||||
f=88200000,m=WFM,bw=200k,d=RMF Polkowice
|
||||
f=88200000,m=WFM,bw=200k,d=RMF Kielce
|
||||
f=89200000,m=WFM,bw=200k,d=RMF Bielsko-Biala
|
||||
f=89300000,m=WFM,bw=200k,d=RMF Lublin
|
||||
f=89300000,m=WFM,bw=200k,d=RMF Koszalin
|
||||
f=90600000,m=WFM,bw=200k,d=RMF Warsaw
|
||||
f=91000000,m=WFM,bw=200k,d=RMF Warsaw
|
||||
f=91300000,m=WFM,bw=200k,d=RMF Łobez
|
||||
f=91500000,m=WFM,bw=200k,d=RMF Ostroleka
|
||||
f=91700000,m=WFM,bw=200k,d=RMF Wagrowiec
|
||||
f=91900000,m=WFM,bw=200k,d=RMF Siedlce
|
||||
f=92600000,m=WFM,bw=200k,d=RMF Elbląg
|
||||
f=92900000,m=WFM,bw=200k,d=RMF Wroclaw
|
||||
f=93000000,m=WFM,bw=200k,d=RMF Katowice
|
||||
f=93300000,m=WFM,bw=200k,d=RMF Bydgoszcz
|
||||
f=93500000,m=WFM,bw=200k,d=RMF Lodz
|
||||
f=93600000,m=WFM,bw=200k,d=RMF Ryki
|
||||
f=93800000,m=WFM,bw=200k,d=RMF Luban
|
||||
f=94300000,m=WFM,bw=200k,d=RMF Płock
|
||||
f=94600000,m=WFM,bw=200k,d=RMF Poznan
|
||||
f=94800000,m=WFM,bw=200k,d=RMF Zagan
|
||||
f=95100000,m=WFM,bw=200k,d=RMF Suwałki
|
||||
f=95300000,m=WFM,bw=200k,d=RMF Olsztyn
|
||||
f=95400000,m=WFM,bw=200k,d=RMF Tarnow
|
||||
f=96000000,m=WFM,bw=200k,d=RMF Krakow
|
||||
f=96100000,m=WFM,bw=200k,d=RMF Legnica
|
||||
f=96100000,m=WFM,bw=200k,d=RMF Gorzow Wielkopolski
|
||||
f=96400000,m=WFM,bw=200k,d=RMF Bialogard
|
||||
f=96600000,m=WFM,bw=200k,d=RMF Walcz
|
||||
f=97100000,m=WFM,bw=200k,d=RMF Włoszczowa
|
||||
f=98000000,m=WFM,bw=200k,d=RMF Kalisz
|
||||
f=98400000,m=WFM,bw=200k,d=RMF Gdansk
|
||||
f=98900000,m=WFM,bw=200k,d=RMF Konin
|
||||
f=99300000,m=WFM,bw=200k,d=RMF Opole
|
||||
f=99500000,m=WFM,bw=200k,d=RMF Kluczbork
|
||||
f=100100000,m=WFM,bw=200k,d=RMF Rzeszow
|
||||
f=100200000,m=WFM,bw=200k,d=RMF Bialystok
|
||||
f=100800000,m=WFM,bw=200k,d=RMF Jelenia Gora
|
||||
f=100900000,m=WFM,bw=200k,d=RMF Slupsk
|
||||
f=101100000,m=WFM,bw=200k,d=RMF Solina
|
||||
f=101200000,m=WFM,bw=200k,d=RMF Swinoujscie
|
||||
f=101600000,m=WFM,bw=200k,d=RMF Klodzko
|
||||
f=101800000,m=WFM,bw=200k,d=RMF Zakopane
|
||||
f=101800000,m=WFM,bw=200k,d=RMF Leżajsk
|
||||
f=102000000,m=WFM,bw=200k,d=RMF Gizycko
|
||||
f=102900000,m=WFM,bw=200k,d=RMF Walbrzych
|
||||
f=103200000,m=WFM,bw=200k,d=RMF Szczawnica
|
||||
f=103400000,m=WFM,bw=200k,d=RMF Przemyśl
|
||||
f=103400000,m=WFM,bw=200k,d=RMF Lebork
|
||||
f=104700000,m=WFM,bw=200k,d=RMF Rabka
|
||||
f=104900000,m=WFM,bw=200k,d=RMF Koszalin
|
||||
f=105900000,m=WFM,bw=200k,d=RMF Krynica
|
||||
f=105900000,m=WFM,bw=200k,d=RMF Czestochowa
|
||||
f=106400000,m=WFM,bw=200k,d=RMF Zielona Gora
|
||||
f=106500000,m=WFM,bw=200k,d=RMF Elk
|
||||
f=106700000,m=WFM,bw=200k,d=RMF Szczecin
|
||||
f=107400000,m=WFM,bw=200k,d=RMF Iława
|
||||
f=107700000,m=WFM,bw=200k,d=RMF Zamosc
|
||||
@@ -1,28 +0,0 @@
|
||||
f=88000000,m=WFM,bw=200k,d=Radio Impuls
|
||||
f=88500000,m=WFM,bw=200k,d=Radio Trinitas
|
||||
f=89000000,m=WFM,bw=200k,d=Radio ZU
|
||||
f=89500000,m=WFM,bw=200k,d=Dance FM
|
||||
f=90800000,m=WFM,bw=200k,d=Magic FM
|
||||
f=91700000,m=WFM,bw=200k,d=Național FM
|
||||
f=92100000,m=WFM,bw=200k,d=One World Radio
|
||||
f=93500000,m=WFM,bw=200k,d=RFI România
|
||||
f=94200000,m=WFM,bw=200k,d=Radio Vocea Evangheliei
|
||||
f=94800000,m=WFM,bw=200k,d=Radio Guerrilla
|
||||
f=96100000,m=WFM,bw=200k,d=Kiss FM
|
||||
f=96900000,m=WFM,bw=200k,d=Gold FM
|
||||
f=97900000,m=WFM,bw=200k,d=Digi FM
|
||||
f=98300000,m=WFM,bw=200k,d=București FM
|
||||
f=99300000,m=WFM,bw=200k,d=Radio Itsy Bitsy
|
||||
f=100200000,m=WFM,bw=200k,d=Virgin Radio
|
||||
f=100600000,m=WFM,bw=200k,d=Rock FM
|
||||
f=101300000,m=WFM,bw=200k,d=Radio România Cultural
|
||||
f=101900000,m=WFM,bw=200k,d=Romantic FM
|
||||
f=102800000,m=WFM,bw=200k,d=PRO FM
|
||||
f=103400000,m=WFM,bw=200k,d=Radio Seven
|
||||
f=103800000,m=WFM,bw=200k,d=Digi 24 FM
|
||||
f=104800000,m=WFM,bw=200k,d=Radio România Muzical
|
||||
f=105300000,m=WFM,bw=200k,d=Radio România Actualități
|
||||
f=105800000,m=WFM,bw=200k,d=Sport Total FM
|
||||
f=106200000,m=WFM,bw=200k,d=3 FM
|
||||
f=106700000,m=WFM,bw=200k,d=Europa FM
|
||||
f=107300000,m=WFM,bw=200k,d=Smart FM
|
||||
@@ -1,94 +0,0 @@
|
||||
f=91200000,m=WFM,bw=200k,d=Radio Slovensko Nitra
|
||||
f=92200000,m=WFM,bw=200k,d=Radio Slovensko Poprad
|
||||
f=96600000,m=WFM,bw=200k,d=Radio Slovensko Bratislava
|
||||
f=96600000,m=WFM,bw=200k,d=Radio Slovensko Kosice
|
||||
f=103500000,m=WFM,bw=200k,d=Radio Slovensko Zilina
|
||||
f=99300000,m=WFM,bw=200k,d=Radio Regina Bratislava
|
||||
f=100100000,m=WFM,bw=200k,d=Radio Regina Zilina
|
||||
f=100300000,m=WFM,bw=200k,d=Radio Regina Kosice
|
||||
f=96200000,m=WFM,bw=200k,d=Radio Devin Kosice
|
||||
f=104400000,m=WFM,bw=200k,d=Radio Devin Bratislava
|
||||
f=106700000,m=WFM,bw=200k,d=Radio Devin Presov
|
||||
f=89300000,m=WFM,bw=200k,d=Radio _FM Bratislava
|
||||
f=94500000,m=WFM,bw=200k,d=Radio _FM Zilina
|
||||
f=101200000,m=WFM,bw=200k,d=Radio _FM Kosice
|
||||
f=101500000,m=WFM,bw=200k,d=Radio _FM Presov
|
||||
f=105400000,m=WFM,bw=200k,d=Radio _FM Banska Bystrica
|
||||
f=98900000,m=WFM,bw=200k,d=Radio Patria Bratislava
|
||||
f=102400000,m=WFM,bw=200k,d=Radio Patria Senec
|
||||
f=98900000,m=WFM,bw=200k,d=Radio Slovakia International Bratislava
|
||||
f=107600000,m=WFM,bw=200k,d=Radio Expres Bratislava
|
||||
f=96500000,m=WFM,bw=200k,d=Radio Expres Zilina
|
||||
f=93100000,m=WFM,bw=200k,d=Radio Expres Martin
|
||||
f=93600000,m=WFM,bw=200k,d=Radio Expres Donovaly
|
||||
f=95100000,m=WFM,bw=200k,d=Radio Expres Banska Stiavnica
|
||||
f=95200000,m=WFM,bw=200k,d=Radio Expres Kosice
|
||||
f=105200000,m=WFM,bw=200k,d=Radio Expres Presov
|
||||
f=99500000,m=WFM,bw=200k,d=Radio Expres Vychodna
|
||||
f=104800000,m=WFM,bw=200k,d=Europa 2 Bratislava
|
||||
f=93500000,m=WFM,bw=200k,d=Europa 2 Martin
|
||||
f=97200000,m=WFM,bw=200k,d=Europa 2 Presov
|
||||
f=102000000,m=WFM,bw=200k,d=Europa 2 Kosice
|
||||
f=106600000,m=WFM,bw=200k,d=Radio Melody Bratislava
|
||||
f=88800000,m=WFM,bw=200k,d=Radio Melody Nitra
|
||||
f=89800000,m=WFM,bw=200k,d=Radio Rock Presov
|
||||
f=97600000,m=WFM,bw=200k,d=Radio Rock Bratislava
|
||||
f=100300000,m=WFM,bw=200k,d=Radio Rock Trencin
|
||||
f=94000000,m=WFM,bw=200k,d=Fun radio Nitra
|
||||
f=94300000,m=WFM,bw=200k,d=Fun radio Bratislava
|
||||
f=102900000,m=WFM,bw=200k,d=Fun radio Kosice
|
||||
f=94800000,m=WFM,bw=200k,d=Radio Vlna Kosice
|
||||
f=96100000,m=WFM,bw=200k,d=Radio Vlna Nitra
|
||||
f=101800000,m=WFM,bw=200k,d=Radio Vlna Bratislava
|
||||
f=104800000,m=WFM,bw=200k,d=Radio Vlna Poprad
|
||||
f=104900000,m=WFM,bw=200k,d=Radio Vlna Martin
|
||||
f=105100000,m=WFM,bw=200k,d=Radio Vlna Banska Stiavnica
|
||||
f=105800000,m=WFM,bw=200k,d=Radio Vlna Presov
|
||||
f=95600000,m=WFM,bw=200k,d=Best FM Bratislava
|
||||
f=93800000,m=WFM,bw=200k,d=Best FM Kosice
|
||||
f=88200000,m=WFM,bw=200k,d=Best FM Nitra
|
||||
f=94000000,m=WFM,bw=200k,d=Best FM Presov
|
||||
f=98900000,m=WFM,bw=200k,d=Best FM Trencin
|
||||
f=93000000,m=WFM,bw=200k,d=Best FM Trnava
|
||||
f=98000000,m=WFM,bw=200k,d=Best FM Zilina
|
||||
f=89400000,m=WFM,bw=200k,d=Radio Lumen Martin
|
||||
f=92900000,m=WFM,bw=200k,d=Radio Lumen Presov
|
||||
f=93800000,m=WFM,bw=200k,d=Radio Lumen Bratislava
|
||||
f=94400000,m=WFM,bw=200k,d=Radio Lumen Kosice
|
||||
f=98100000,m=WFM,bw=200k,d=Radio Lumen Trnava
|
||||
f=91700000,m=WFM,bw=200k,d=Radio 7 Bratislava
|
||||
f=88600000,m=WFM,bw=200k,d=Radio 7 Kosice
|
||||
f=103600000,m=WFM,bw=200k,d=Radio 7 Nitra
|
||||
f=96400000,m=WFM,bw=200k,d=Radio 7 Poprad
|
||||
f=107500000,m=WFM,bw=200k,d=Radio 7 Ruzomberok
|
||||
f=101000000,m=WFM,bw=200k,d=Radio 7 Zilina
|
||||
f=104100000,m=WFM,bw=200k,d=Dobre radio Presov
|
||||
f=104200000,m=WFM,bw=200k,d=Dobre radio Domasa
|
||||
f=104500000,m=WFM,bw=200k,d=Dobre radio Kosice
|
||||
f=107000000,m=WFM,bw=200k,d=Dobre radio Bratislava
|
||||
f=89700000,m=WFM,bw=200k,d=Radio Beta Zvolen
|
||||
f=104300000,m=WFM,bw=200k,d=Radio Beta Trencin
|
||||
f=96200000,m=WFM,bw=200k,d=Radio Frontinus Martin
|
||||
f=104600000,m=WFM,bw=200k,d=Radio Frontinus Zilina
|
||||
f=99600000,m=WFM,bw=200k,d=Radio Rebeca Martin
|
||||
f=88600000,m=WFM,bw=200k,d=Radio Rebeca Zilina
|
||||
f=96000000,m=WFM,bw=200k,d=Radio SiTy Bratislava
|
||||
f=97700000,m=WFM,bw=200k,d=Sky Radio Kosice
|
||||
f=91400000,m=WFM,bw=200k,d=Sky Radio Presov
|
||||
f=106600000,m=WFM,bw=200k,d=Sky Radio Poprad
|
||||
f=106200000,m=WFM,bw=200k,d=Trnavske radio Trnava
|
||||
f=107900000,m=WFM,bw=200k,d=Trnavske radio Piestany
|
||||
f=93300000,m=WFM,bw=200k,d=Radio VIVA Bratislava
|
||||
f=101000000,m=WFM,bw=200k,d=Radio VIVA Trnava
|
||||
f=93600000,m=WFM,bw=200k,d=Radio WOW Nitra
|
||||
f=106700000,m=WFM,bw=200k,d=Radio WOW Trencin
|
||||
f=89200000,m=WFM,bw=200k,d=Zahoracke radio Skalica
|
||||
f=101500000,m=WFM,bw=200k,d=Zahoracke radio Senica
|
||||
f=92400000,m=WFM,bw=200k,d=Radio Goldies Martin
|
||||
f=92800000,m=WFM,bw=200k,d=Radio Goldies Trencin
|
||||
f=104100000,m=WFM,bw=200k,d=Radio Kiss Presov
|
||||
f=90100000,m=WFM,bw=200k,d=Radio Kosice Bratislava
|
||||
f=90400000,m=WFM,bw=200k,d=Radio Kosice Presov
|
||||
f=91700000,m=WFM,bw=200k,d=Radio Kosice Kosice
|
||||
f=106400000,m=WFM,bw=200k,d=Radio Liptov Liptovsky Mikulas
|
||||
f=106100000,m=WFM,bw=200k,d=Radio Modra
|
||||
@@ -1,28 +0,0 @@
|
||||
f=129400000,m=AM,bw=DSB 6k,d=Bucharest Information (LRBB_FSS)
|
||||
f=134380000,m=AM,bw=DSB 6k,d=Bucharest Radar [sector consolidat] (LRBB_CTR)
|
||||
f=119415000,m=AM,bw=DSB 6k,d=Bucharest Approach (LROP_APP)
|
||||
f=127155000,m=AM,bw=DSB 6k,d=Bucharest Director (LROP_D_APP)
|
||||
f=122905000,m=AM,bw=DSB 6k,d=Constanta Approach (LRCK_APP)
|
||||
f=126430000,m=AM,bw=DSB 6k,d=NAPOC Approach (LRCL_APP)
|
||||
f=126430000,m=AM,bw=DSB 6k,d=NAPOC North Approach (LRCL_N_APP)
|
||||
f=119680000,m=AM,bw=DSB 6k,d=NAPOC South Approach (LRCL_S_APP)
|
||||
f=123530000,m=AM,bw=DSB 6k,d=Arad Approach (LRAR_APP)
|
||||
f=118230000,m=AM,bw=DSB 6k,d=Arad (LRAR_TWR)
|
||||
f=120980000,m=AM,bw=DSB 6k,d=Bacau (LRBC_TWR)
|
||||
f=118855000,m=AM,bw=DSB 6k,d=Baia Mare (LRBM_TWR)
|
||||
f=125205000,m=AM,bw=DSB 6k,d=Baneasa (LRBS_TWR)
|
||||
f=118705000,m=AM,bw=DSB 6k,d=Cluj (LRCL_TWR)
|
||||
f=120030000,m=AM,bw=DSB 6k,d=Constanta (LRCK_TWR)
|
||||
f=129530000,m=AM,bw=DSB 6k,d=Craiova (LRCV_TWR)
|
||||
f=119955000,m=AM,bw=DSB 6k,d=Iasi (LRIA_TWR)
|
||||
f=118805000,m=AM,bw=DSB 6k,d=Otopeni (LROP_TWR)
|
||||
f=118455000,m=AM,bw=DSB 6k,d=Oradea (LROD_TWR)
|
||||
f=119655000,m=AM,bw=DSB 6k,d=Satu Mare (LRSM_TWR)
|
||||
f=121305000,m=AM,bw=DSB 6k,d=Sibiu (LRSB_TWR)
|
||||
f=129955000,m=AM,bw=DSB 6k,d=Suceava (LRSV_TWR)
|
||||
f=120105000,m=AM,bw=DSB 6k,d=Timisoara (LRTR_TWR)
|
||||
f=119180000,m=AM,bw=DSB 6k,d=Targu Mures (LRTM_TWR)
|
||||
f=119755000,m=AM,bw=DSB 6k,d=Tulcea (LRTC_TWR)
|
||||
f=129950000,m=AM,bw=DSB 6k,d=Baneasa (LRBS_GND)
|
||||
f=121855000,m=AM,bw=DSB 6k,d=Otopeni (LROP_GND)
|
||||
f=121600000,m=AM,bw=DSB 6k,d=Timisoara (LRTR_GND)
|
||||
@@ -1,62 +0,0 @@
|
||||
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
|
||||
# Lower Paging Bands - Paired
|
||||
f=152030000,d=USA LPBP-FA1
|
||||
f=152060000,d=USA LPBP-FB1
|
||||
f=152090000,d=USA LPBP-FC1
|
||||
f=152120000,d=USA LPBP-FD1
|
||||
f=152150000,d=USA LPBP-FE1
|
||||
f=152180000,d=USA LPBP-FF1
|
||||
f=152210000,d=USA LPBP-FG1
|
||||
f=152510000,d=USA LPBP-FH1
|
||||
f=152540000,d=USA LPBP-FI1
|
||||
f=152570000,d=USA LPBP-FJ1
|
||||
f=152600000,d=USA LPBP-FK1
|
||||
f=152630000,d=USA LPBP-FL1
|
||||
f=152660000,d=USA LPBP-FM1
|
||||
f=152690000,d=USA LPBP-FN1
|
||||
f=152720000,d=USA LPBP-FO1
|
||||
f=152750000,d=USA LPBP-FP1
|
||||
f=152780000,d=USA LPBP-FQ1
|
||||
f=152810000,d=USA LPBP-FR1
|
||||
f=158490000,d=USA LPBP-FA2
|
||||
f=158520000,d=USA LPBP-FB2
|
||||
f=158550000,d=USA LPBP-FC2
|
||||
f=158580000,d=USA LPBP-FD2
|
||||
f=158610000,d=USA LPBP-FE2
|
||||
f=158640000,d=USA LPBP-FF2
|
||||
f=158670000,d=USA LPBP-FG2
|
||||
f=157770000,d=USA LPBP-FH2
|
||||
f=157800000,d=USA LPBP-FI2
|
||||
f=157830000,d=USA LPBP-FJ2
|
||||
f=157860000,d=USA LPBP-FK2
|
||||
f=157890000,d=USA LPBP-FL2
|
||||
f=157920000,d=USA LPBP-FM2
|
||||
f=157950000,d=USA LPBP-FN2
|
||||
f=157980000,d=USA LPBP-FO2
|
||||
f=158010000,d=USA LPBP-FP2
|
||||
f=158040000,d=USA LPBP-FQ2
|
||||
f=158070000,d=USA LPBP-FR2
|
||||
f=454025000,d=USA LPBP-GA1
|
||||
f=454050000,d=USA LPBP-GB1
|
||||
f=454075000,d=USA LPBP-GC1
|
||||
f=454100000,d=USA LPBP-GD1
|
||||
f=454125000,d=USA LPBP-GE1
|
||||
f=454150000,d=USA LPBP-GF1
|
||||
f=454175000,d=USA LPBP-GG1
|
||||
f=454200000,d=USA LPBP-GH1
|
||||
f=454225000,d=USA LPBP-GI1
|
||||
f=454250000,d=USA LPBP-GJ1
|
||||
f=454275000,d=USA LPBP-GK1
|
||||
f=454300000,d=USA LPBP-GL1
|
||||
f=454325000,d=USA LPBP-GM1
|
||||
f=152570000,d=USA LPBP-GN1
|
||||
f=152600000,d=USA LPBP-GO1
|
||||
f=152630000,d=USA LPBP-GP1
|
||||
f=152660000,d=USA LPBP-GQ1
|
||||
f=152690000,d=USA LPBP-GR1
|
||||
f=152720000,d=USA LPBP-GS1
|
||||
f=152750000,d=USA LPBP-GT1
|
||||
f=152780000,d=USA LPBP-GU1
|
||||
f=152810000,d=USA LPBP-GV1
|
||||
f=152720000,d=USA LPBP-GX1
|
||||
f=152750000,d=USA LPBP-GY1
|
||||
@@ -1,28 +0,0 @@
|
||||
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
|
||||
# Lower Paging Bands - Paired
|
||||
f=152810000,d=USA LPBP-GZ1
|
||||
f=459025000,d=USA LPBP-GA2
|
||||
f=459050000,d=USA LPBP-GB2
|
||||
f=459075000,d=USA LPBP-GC2
|
||||
f=459100000,d=USA LPBP-GD2
|
||||
f=459125000,d=USA LPBP-GE2
|
||||
f=459150000,d=USA LPBP-GF2
|
||||
f=459175000,d=USA LPBP-GG2
|
||||
f=459200000,d=USA LPBP-GH2
|
||||
f=459225000,d=USA LPBP-GI2
|
||||
f=459250000,d=USA LPBP-GJ2
|
||||
f=459275000,d=USA LPBP-GK2
|
||||
f=459300000,d=USA LPBP-GL2
|
||||
f=459325000,d=USA LPBP-GM2
|
||||
f=157830000,d=USA LPBP-GN2
|
||||
f=157860000,d=USA LPBP-GO2
|
||||
f=157890000,d=USA LPBP-GP2
|
||||
f=157920000,d=USA LPBP-GQ2
|
||||
f=157950000,d=USA LPBP-GR2
|
||||
f=157980000,d=USA LPBP-GS2
|
||||
f=158010000,d=USA LPBP-GT2
|
||||
f=158040000,d=USA LPBP-GU2
|
||||
f=158070000,d=USA LPBP-GV2
|
||||
f=157980000,d=USA LPBP-GX2
|
||||
f=158010000,d=USA LPBP-GY2
|
||||
f=158070000,d=USA LPBP-GZ2
|
||||
@@ -1,38 +0,0 @@
|
||||
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
|
||||
# Lower Paging Bands - Unpaired
|
||||
f=35200000,d=USA LPBU-CA
|
||||
f=35220000,d=USA LPBU-CB
|
||||
f=35240000,d=USA LPBU-CC
|
||||
f=35260000,d=USA LPBU-CD
|
||||
f=35300000,d=USA LPBU-CE
|
||||
f=35340000,d=USA LPBU-CF
|
||||
f=35380000,d=USA LPBU-CG
|
||||
f=35420000,d=USA LPBU-CH
|
||||
f=35460000,d=USA LPBU-CI
|
||||
f=35500000,d=USA LPBU-CJ
|
||||
f=35540000,d=USA LPBU-CK
|
||||
f=35560000,d=USA LPBU-CL
|
||||
f=35580000,d=USA LPBU-CM
|
||||
f=35600000,d=USA LPBU-CN
|
||||
f=35620000,d=USA LPBU-CO
|
||||
f=35660000,d=USA LPBU-CP
|
||||
f=43200000,d=USA LPBU-DA
|
||||
f=43220000,d=USA LPBU-DB
|
||||
f=43240000,d=USA LPBU-DC
|
||||
f=43260000,d=USA LPBU-DD
|
||||
f=43300000,d=USA LPBU-DE
|
||||
f=43340000,d=USA LPBU-DF
|
||||
f=43380000,d=USA LPBU-DG
|
||||
f=43420000,d=USA LPBU-DH
|
||||
f=43460000,d=USA LPBU-DI
|
||||
f=43500000,d=USA LPBU-DJ
|
||||
f=43540000,d=USA LPBU-DK
|
||||
f=43560000,d=USA LPBU-DL
|
||||
f=43580000,d=USA LPBU-DM
|
||||
f=43600000,d=USA LPBU-DN
|
||||
f=43620000,d=USA LPBU-DO
|
||||
f=43660000,d=USA LPBU-DP
|
||||
f=152240000,d=USA LPBU-EA
|
||||
f=152800000,d=USA LPBU-EB
|
||||
f=158100000,d=USA LPBU-EC
|
||||
f=158700000,d=USA LPBU-ED
|
||||
@@ -1,52 +0,0 @@
|
||||
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
|
||||
# Upper Paging Bands - Private Carrier Paging
|
||||
f=929012500,d=USA PCP-A
|
||||
f=929112500,d=USA PCP-B
|
||||
f=929237500,d=USA PCP-C
|
||||
f=929312500,d=USA PCP-D
|
||||
f=929387500,d=USA PCP-E
|
||||
f=929437500,d=USA PCP-F
|
||||
f=929462500,d=USA PCP-G
|
||||
f=929637500,d=USA PCP-H
|
||||
f=929687500,d=USA PCP-I
|
||||
f=929787500,d=USA PCP-J
|
||||
f=929912500,d=USA PCP-K
|
||||
f=929962500,d=USA PCP-L
|
||||
# Upper Paging Bands - Common Carrier Paging
|
||||
f=931012500,d=USA CCP-AA
|
||||
f=931037500,d=USA CCP-AB
|
||||
f=931062500,d=USA CCP-AC
|
||||
f=931087500,d=USA CCP-AD
|
||||
f=931112500,d=USA CCP-AE
|
||||
f=931137500,d=USA CCP-AF
|
||||
f=931162500,d=USA CCP-AG
|
||||
f=931187500,d=USA CCP-AH
|
||||
f=931212500,d=USA CCP-AI
|
||||
f=931237500,d=USA CCP-AJ
|
||||
f=931262500,d=USA CCP-AK
|
||||
f=931287500,d=USA CCP-AL
|
||||
f=931312500,d=USA CCP-AM
|
||||
f=929462500,d=USA CCP-AN
|
||||
f=929637500,d=USA CCP-AO
|
||||
f=929687500,d=USA CCP-AP
|
||||
f=929787500,d=USA CCP-AQ
|
||||
f=929912500,d=USA CCP-AR
|
||||
f=929962500,d=USA CCP-AS
|
||||
f=929462500,d=USA CCP-AT
|
||||
f=929637500,d=USA CCP-AU
|
||||
f=929687500,d=USA CCP-AV
|
||||
f=929787500,d=USA CCP-AW
|
||||
f=929912500,d=USA CCP-AX
|
||||
f=929962500,d=USA CCP-AY
|
||||
f=929962500,d=USA CCP-AZ
|
||||
f=931662500,d=USA CCP-BA
|
||||
f=931687500,d=USA CCP-BB
|
||||
f=931712500,d=USA CCP-BC
|
||||
f=931737500,d=USA CCP-BD
|
||||
f=931762500,d=USA CCP-BE
|
||||
f=931787500,d=USA CCP-BF
|
||||
f=931812500,d=USA CCP-BG
|
||||
f=931837500,d=USA CCP-BH
|
||||
f=931862500,d=USA CCP-BI
|
||||
f=931962500,d=USA CCP-BJ
|
||||
f=931987500,d=USA CCP-BK
|
||||
@@ -0,0 +1,3 @@
|
||||
2402000000
|
||||
2426000000
|
||||
2480000000
|
||||
@@ -0,0 +1,10 @@
|
||||
433920000
|
||||
315000000
|
||||
418000000
|
||||
303000000
|
||||
310000000
|
||||
330000000
|
||||
350000000
|
||||
370000000
|
||||
390000000
|
||||
430500000
|
||||
@@ -0,0 +1,2 @@
|
||||
315000000
|
||||
433920000
|
||||
@@ -0,0 +1,10 @@
|
||||
Filetype: Flipper SubGhz RAW File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok650Async
|
||||
Protocol: RAW
|
||||
|
||||
RAW_Data: 210 650 210 650 210 650 630 650 210 650 210 650 210 650 630 650
|
||||
RAW_Data: 210 650 210 650 210 650 630 650 210 650 210 650 210 650 210 1950
|
||||
RAW_Data: 210 650 210 650 210 650 630 650 210 650 210 650 210 650 630 650
|
||||
RAW_Data: 210 650 210 650 210 650 630 650 210 650 210 650 210 650 210 1950
|
||||
Reference in New Issue
Block a user