Compare commits

..

21 Commits

Author SHA1 Message Date
E.T bb1b703dc1 Add vscode debug configuration as a template (#2109) 2024-04-17 20:21:16 +08:00
eried 990f63e5de Adjusting a bit the issue templates for clarity 2024-04-17 10:48:24 +02:00
Erwin Ried 6fe8ab4f99 Delete .github/ISSUE_TEMPLATE/03_problem-upgrading-the-firmware.md 2024-04-17 10:43:24 +02:00
Erwin Ried d6ed12191d Create 03_problem-upgrading-the-firmware.yml 2024-04-17 10:41:25 +02:00
Totoo 8b159f447a Smaller freq changes with encoder on SubghzD and Weather Station (#2106) 2024-04-16 14:20:12 +08:00
Totoo e9c32504d1 Pocsagtxserial (#2099)
* sendpocsag first test
* optimize
* nicer
* overflow
2024-04-11 15:10:59 +02:00
gullradriel 004799e1a3 Contributors scroll (#2093)
* adding on_right

* menu view instead of console

* fixing on_right typo

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-08 13:45:33 +08:00
jLynx 8e90c65a62 v2.0.1 (#2090)
* Update version.txt

* Update past_version.txt
2024-04-07 16:24:01 +12:00
gullradriel bfe67a13b7 removed dead link (#2089)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-07 00:59:40 +08:00
gullradriel f091db3270 Mic audio ak4951 fix + SSTV description (#2088)
* fixed too long description, thanks to ImDroided

* fixed ak4951 RxAudio not working on startup when enabled

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-05 16:29:18 -05:00
Mark Thompson a602abf1d8 Fix Cancel button in Settings->Autostart (#2087) 2024-04-05 16:04:39 +02:00
ImDroided 689224fd8d Added CubeSats to Presets 2024-04-05 08:52:14 +02:00
gullradriel 23cf1c2737 fix build warning in settings (#2081)
* fix warning ui::SetAutostartView::opts' should be initialized in the member initialization list

* clang

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-03 14:17:48 -05:00
Totoo c48cbb7e55 Autostart option (#2079)
* Autostart app on boot
* Settings page
* ext app support
2024-04-03 16:27:13 +02:00
Totoo 3665b3c607 Added sil value ASDB (#2078)
* Added sil value. resolves #2005

* readibility
2024-04-03 15:57:39 +08:00
Totoo 804b7c87b7 allow to load more files but unordered (#2077) 2024-04-02 20:19:28 +02:00
Totoo a30875b582 Fileman memory opt (#2076)
* paginator
* fixes
* string instead of path, so saves ram.
* fix FileLoadView
* pagination list number
* pagination "size" test
* added detection of special folder name to display page numbers instead of (0 or a number of byte)
* order of things: only one directory '..', only on first page
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-04-02 17:02:02 +02:00
Mark Thompson 56303eb385 Use freq from AppSettings unless passed from another app (#2073) 2024-03-30 12:57:43 -05:00
Totoo d29172f41e Add GPS icon, fix overwrite bug (#2072) 2024-03-30 16:48:45 +01:00
sommermorgentraum 6d9d5ad1af fix read file (#2070) 2024-03-30 13:03:27 +01:00
gullradriel 2acefee4d2 adjust GUI for more preset name room (#2068) 2024-03-30 10:34:37 +01:00
47 changed files with 863 additions and 254 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
name: Bug report
description: File a bug reports regarding the firmware.
labels: ['bug']
labels:
- bug
body:
- type: markdown
attributes:
@@ -1,6 +1,7 @@
name: Feature Request
description: For feature requests regarding the firmware.
labels: ['feature request']
labels:
- enhancement
body:
- type: markdown
attributes:
@@ -1,44 +0,0 @@
---
name: Problem upgrading the firmware or booting
about: If you are having firmware upgrade or booting problems
title: ''
labels: 'firmware'
assignees: ''
---
----
Before creating this issue, **do the following**:
* Read the Wiki on booting: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
* Read: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
* Watch carefully: https://www.youtube.com/watch?v=_zx4ZvurgOs
* (if you are not in Windows) also check: https://www.youtube.com/watch?v=kjFB58Y1TAo
----
**Describe the issue**
A clear and concise description of what the issue you are facing is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Tap on '....'
**My Hardware**
Please specify what PortaPack hardware version you are using.
You can find the list of versions here: https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions
**Affected versions**
Please tell us what version you are running.
If your device is still functional, try the latest nightly release before submitting this.
You can find the latest nightly version here https://github.com/portapack-mayhem/mayhem-firmware/releases
**Were you able to update the firmware before?**
Things might be confusing the first time, please check the video available on the link above.
**Can you try the upgrade with a different PC/Portapack/HackRF?**
If is possible, swap hardware and try again. Also, try different USB cables, even if the one you are using works fine for other purposes.
**Additional**
If the issue is difficult to explain, additionally to the text please include images and videos.
@@ -0,0 +1,45 @@
name: Firmware/boot issue
description: If you are having firmware upgrade or booting problems.
labels:
- firmware
body:
- type: markdown
attributes:
value: >2-
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Mayhem firmware.
Before creating this issue, **do the following**:
* Read the Wiki on booting:
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
* Read:
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
* Watch carefully: https://www.youtube.com/watch?v=_zx4ZvurgOs
* (if you are not in Windows) also check:
https://www.youtube.com/watch?v=kjFB58Y1TAo
* Check hardware versions:
https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions
- type: textarea
id: problem
attributes:
label: What happened?
description: If after following the above troubleshooting the problem still remains
placeholder: Describe what was is the problem
- type: input
id: hardware
attributes:
label: Your hardware
description: Check the link with the versions above
placeholder: Which device you have?
- type: checkboxes
id: firmware_update_before
attributes:
label: Tests
description: >-
Things might be confusing the first time, please check the documentation
above
options:
- label: I managed to update/boot previously
required: false
+1 -1
View File
@@ -1 +1 @@
v1.9.1
v2.0.0
+1 -1
View File
@@ -1 +1 @@
v2.0.0
v2.0.1
+148
View File
@@ -0,0 +1,148 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) JTAG probe",
"type": "cppdbg",
"request": "launch",
"miDebuggerPath": "arm-none-eabi-gdb",
"targetArchitecture": "arm",
"program": "${workspaceRoot}/build/firmware/baseband/baseband_adsbrx.elf",
"cwd": "${workspaceRoot}",
"setupCommands": [
// use logging for troubleshooting
//{"text": "set logging file ${workspaceRoot}/build/arm-none-eabi-gdb.log"},
//{"text": "set logging on"},
{
"text": "file '${workspaceRoot}/build/firmware/baseband/baseband_adsbrx.elf'"
},
{
"text": "target extended-remote /dev/ttyACM0"
},
{
"text": "monitor swdp_scan"
},
{
"text": "attach 1"
},
],
"launchCompleteCommand": "None",
"externalConsole": false,
},
{
"name": "(gdb) OpenOCD m4 baseband",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/firmware/baseband/baseband_sd_over_usb.elf",
"args": [],
"cwd": "${workspaceRoot}",
// "environment": [
// {
// "name": "PATH",
// "value": "${env:PATH}"
// }
// ],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "arm-none-eabi-gdb",
"targetArchitecture": "arm",
"debugServerPath": "openocd",
"debugServerArgs": "-f interface/ftdi/um232h.cfg -f target/lpc4350.cfg -c \"gdb_breakpoint_override hard\"",
"serverStarted": "Listening on port [0-9]+ for gdb connections",
"filterStderr": true,
"filterStdout": false,
"launchCompleteCommand": "None",
"postRemoteConnectCommands": [
{
"description": "Target Remote Device on Port 3333",
"text": "target extended-remote :3333",
"ignoreFailures": false
},
{
"description": "Respect Hardware Limitations",
"text": "set remote hardware-watchpoint-limit 2",
"ignoreFailures": false
},
{
"description": "Respect Hardware Limitations",
"text": "set remote hardware-breakpoint-limit 4",
"ignoreFailures": false
},
{
"description": "Shutdown GDB Server on GDB Detach",
"text": "monitor [target current] configure -event gdb-detach { shutdown }",
"ignoreFailures": false
},
],
"stopAtConnect": false,
"logging": {
"exceptions": true,
"engineLogging": false,
"moduleLoad": true,
"programOutput": true,
"trace": false,
"traceResponse": false
},
"useExtendedRemote": true
},
{
"name": "(gdb) OpenOCD m0 application",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/firmware/application/application.elf",
"args": [],
"cwd": "${workspaceRoot}",
// "environment": [
// {
// "name": "PATH",
// "value": "${env:PATH}"
// }
// ],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "arm-none-eabi-gdb",
"targetArchitecture": "arm",
"debugServerPath": "openocd",
"debugServerArgs": "-f interface/ftdi/um232h.cfg -f target/lpc4350.cfg -c \"gdb_breakpoint_override hard\"",
"serverStarted": "Listening on port [0-9]+ for gdb connections",
"filterStderr": true,
"filterStdout": false,
"launchCompleteCommand": "None",
"postRemoteConnectCommands": [
{
"description": "Target Remote Device on Port 3334",
"text": "target extended-remote :3334",
"ignoreFailures": false
},
{
"description": "Respect Hardware Limitations",
"text": "set remote hardware-watchpoint-limit 1",
"ignoreFailures": false
},
{
"description": "Respect Hardware Limitations",
"text": "set remote hardware-breakpoint-limit 2",
"ignoreFailures": false
},
{
"description": "Shutdown GDB Server on GDB Detach",
"text": "monitor [target current] configure -event gdb-detach { shutdown }",
"ignoreFailures": false
},
],
"stopAtConnect": false,
"logging": {
"exceptions": true,
"engineLogging": false,
"moduleLoad": true,
"programOutput": true,
"trace": false,
"traceResponse": false
},
"useExtendedRemote": true
},
]
}
-2
View File
@@ -30,8 +30,6 @@ This repository expands upon the previous work by many people and aims to consta
:heavy_check_mark: Our friends at OpenSourceSDRLab give away five units every three months in our discord (check the badge on top) of their [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too by ordering.
:heavy_check_mark: Another popular option is the clone of the [PortaPack H1](https://s.click.aliexpress.com/e/_Dkbqs2X).
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
![image](https://user-images.githubusercontent.com/1091420/214579017-9ad970b9-0917-48f6-a550-588226d3f89b.png)
+2 -6
View File
@@ -175,16 +175,12 @@ void copy_to_radio_model(const AppSettings& settings) {
// Specifically 'modulation' which requires a running baseband.
if (flags_enabled(settings.mode, Mode::TX)) {
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
persistent_memory::set_target_frequency(settings.tx_frequency);
persistent_memory::set_target_frequency(settings.tx_frequency);
transmitter_model.configure_from_app_settings(settings);
}
if (flags_enabled(settings.mode, Mode::RX)) {
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
persistent_memory::set_target_frequency(settings.rx_frequency);
persistent_memory::set_target_frequency(settings.rx_frequency);
receiver_model.configure_from_app_settings(settings);
receiver_model.set_configuration_without_update(
static_cast<ReceiverModel::Mode>(settings.modulation),
-3
View File
@@ -118,9 +118,6 @@ enum class Mode : uint8_t {
enum class Options {
None = 0x0000,
/* Don't use target frequency from app settings. */
UseGlobalTargetFrequency = 0x0001,
};
/* NB: See RX/TX model headers for default values. */
@@ -208,7 +208,9 @@ AnalogAudioView::AnalogAudioView(
NavigationView& nav,
ReceiverModel::settings_t override)
: AnalogAudioView(nav) {
// Settings to override when launched from another app (versus from AppSettings .ini file)
// TODO: Which other settings make sense to override?
field_frequency.set_value(override.frequency_app_override);
on_frequency_step_changed(override.frequency_step);
options_modulation.set_by_value(toUType(override.mode));
}
@@ -174,7 +174,6 @@ class AnalogAudioView : public View {
app_settings::SettingsManager settings_{
"rx_audio",
app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency,
{
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
}};
@@ -104,6 +104,14 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
};
}
CaptureAppView::CaptureAppView(
NavigationView& nav,
ReceiverModel::settings_t override)
: CaptureAppView(nav) {
// Settings to override when launched from another app (versus from AppSettings .ini file)
field_frequency.set_value(override.frequency_app_override);
}
CaptureAppView::~CaptureAppView() {
receiver_model.disable();
baseband::shutdown();
+2 -2
View File
@@ -38,6 +38,7 @@ namespace ui {
class CaptureAppView : public View {
public:
CaptureAppView(NavigationView& nav);
CaptureAppView(NavigationView& nav, ReceiverModel::settings_t override);
~CaptureAppView();
void focus() override;
@@ -52,8 +53,7 @@ class CaptureAppView : public View {
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
app_settings::SettingsManager settings_{
"rx_capture", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
"rx_capture", app_settings::Mode::RX};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
+93 -83
View File
@@ -1,83 +1,93 @@
#include "ui_about_simple.hpp"
namespace ui {
AboutView::AboutView(NavigationView& nav) {
add_children({&console, &button_ok});
button_ok.on_select = [&nav](Button&) {
nav.pop();
};
console.writeln(STR_COLOR_LIGHT_GREY "List of contributors:");
console.writeln("");
}
void AboutView::update() {
if (++timer > 400) {
timer = 0;
switch (++frame) {
case 1:
// TODO: Generate this automatically from github
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Mayhem:");
console.writeln("eried,euquiq,gregoryfenton");
console.writeln("johnelder,jwetzell,nnemanjan00");
console.writeln("N0vaPixel,klockee,gullradriel");
console.writeln("jamesshao8,ITAxReal,rascafr");
console.writeln("mcules,dqs105,strijar");
console.writeln("zhang00963,RedFox-Fr,aldude999");
console.writeln("East2West,fossum,ArjanOnwezen");
console.writeln("vXxOinvizioNxX,teixeluis");
console.writeln("Brumi-2021,texasyojimbo");
console.writeln("heurist1,intoxsick,ckuethe");
console.writeln("notpike,jLynx,zigad");
console.writeln("MichalLeonBorsuk,jimilinuxguy");
console.writeln("kallanreed,bernd-herzog");
break;
case 2:
console.writeln("NotherNgineer,zxkmm,u-foka");
console.writeln("Netro,HTotoo");
console.writeln("");
break;
case 3:
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Havoc:");
console.writeln("furrtek,mrmookie,NotPike");
console.writeln("mjwaxios,ImDroided,Giorgiofox");
console.writeln("F4GEV,z4ziggy,xmycroftx");
console.writeln("troussos,silascutler");
console.writeln("nickbouwhuis,msoose,leres");
console.writeln("joakar,dhoetger,clem-42");
console.writeln("brianlechthaler,ZeroChaos-...");
console.writeln("");
break;
case 4:
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "PortaPack:");
console.writeln("jboone,argilo");
console.writeln("");
break;
case 5:
// https://github.com/mossmann/hackrf/graphs/contributors
console.writeln(STR_COLOR_DARK_YELLOW "HackRF:");
console.writeln("mossmann,dominicgs,bvernoux");
console.writeln("bgamari,schneider42,miek");
console.writeln("willcode,hessu,Sec42");
console.writeln("yhetti,ckuethe,smunaut");
console.writeln("wishi,mrbubble62,scateu...");
console.writeln("");
frame = 0; // Loop
break;
}
}
}
void AboutView::focus() {
button_ok.focus();
}
} /* namespace ui */
#include "ui_about_simple.hpp"
namespace ui {
// TODO: Generate this automatically from github
// Information: a line starting with a '#' will be yellow coloured
const std::string authors_list[] = {
" ",
"# * List of contributors * ",
" ",
"#Mayhem:",
" ",
"eried,euquiq,gregoryfenton",
"johnelder,jwetzell,nnemanjan00",
"N0vaPixel,klockee,gullradriel",
"jamesshao8,ITAxReal,rascafr",
"mcules,dqs105,strijar",
"zhang00963,RedFox-Fr,aldude999",
"East2West,fossum,ArjanOnwezen",
"vXxOinvizioNxX,teixeluis",
"Brumi-2021,texasyojimbo",
"heurist1,intoxsick,ckuethe",
"notpike,jLynx,zigad",
"MichalLeonBorsuk,jimilinuxguy",
"kallanreed,bernd-herzog",
"NotherNgineer,zxkmm,u-foka",
"Netro,HTotoo",
" ",
"#Havoc:",
" ",
"furrtek,mrmookie,NotPike",
"mjwaxios,ImDroided,Giorgiofox",
"F4GEV,z4ziggy,xmycroftx",
"troussos,silascutler",
"nickbouwhuis,msoose,leres",
"joakar,dhoetger,clem-42",
"brianlechthaler,ZeroChaos-...",
" ",
"#PortaPack:",
" ",
"jboone,argilo",
" ",
"#HackRF:",
" ",
"mossmann,dominicgs,bvernoux",
"bgamari,schneider42,miek",
"willcode,hessu,Sec42",
"yhetti,ckuethe,smunaut",
"wishi,mrbubble62,scateu..."};
AboutView::AboutView(NavigationView& nav) {
add_children({&menu_view,
&button_ok});
button_ok.on_select = [&nav](Button&) {
nav.pop();
};
menu_view.on_left = [this]() {
button_ok.focus();
};
menu_view.on_right = [this]() {
button_ok.focus();
};
for (const std::string& authors_line : authors_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] == '#') {
menu_view.add_item(
{authors_line.substr(1, authors_line.size() - 1),
ui::Color::yellow(),
nullptr,
nullptr});
} else {
menu_view.add_item(
{authors_line,
ui::Color::white(),
nullptr,
nullptr});
}
}
}
}
void AboutView::focus() {
menu_view.focus();
// put focus on first text line
menu_view.set_highlighted(1);
}
} /* namespace ui */
+3 -12
View File
@@ -12,25 +12,16 @@ class AboutView : public View {
AboutView(NavigationView& nav);
void focus() override;
std::string title() const override { return "About"; };
int32_t timer{180};
short frame{0};
private:
void update();
Console console{
{0, 10, 240, 240}};
MenuView menu_view{
{0, 0, 240, 240},
true};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
"OK",
};
MessageHandlerRegistration message_handler_update{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->update();
}};
};
} // namespace ui
+7 -4
View File
@@ -107,7 +107,8 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
" Spd: " + to_string_dec_int(log_entry.vel.speed);
if (log_entry.sil != 0)
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
log_file.write_entry(log_line);
}
@@ -354,11 +355,12 @@ void ADSBRxDetailsView::refresh_ui() {
text_callsign.set(entry_.callsign);
text_infos.set(entry_.info_string);
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
" Spd:" + to_string_dec_int(entry_.velo.speed));
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
else
text_info2.set("");
text_info2.set(str_sil);
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
@@ -471,7 +473,6 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
"Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(std::move(str_info));
}
@@ -479,6 +480,8 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
entry.set_frame_velo(frame);
log_entry.vel = entry.velo;
log_entry.vel_type = msg_sub;
} else if (msg_type == AIRBORNE_OP_STATUS) { // for ver 1
entry.sil = frame.get_sil_value();
}
}
+5
View File
@@ -54,6 +54,8 @@ namespace ui {
#define AIRBORNE_POS_GPS_L 20 // airborne position (lowest type id)
#define AIRBORNE_POS_GPS_H 22 // airborne position (highest type id)
#define AIRBORNE_OP_STATUS 31 // Aircraft operation status
#define RESERVED_L 23 // reserved for other uses
#define RESERVED_H 31 // reserved for other uses
@@ -102,6 +104,8 @@ struct AircraftRecentEntry {
std::string callsign{};
std::string info_string{};
uint8_t sil{0}; // Surveillance integrity level
AircraftRecentEntry(const uint32_t ICAO_address)
: ICAO_address{ICAO_address} {
this->icao_str = to_string_hex(ICAO_address, 6);
@@ -173,6 +177,7 @@ struct ADSBLogEntry {
adsb_pos pos{};
adsb_vel vel{};
uint8_t vel_type{};
uint8_t sil{};
};
// TODO: Make logging optional.
+198 -44
View File
@@ -62,7 +62,7 @@ std::string truncate(const fs::path& path, size_t max_length) {
}
// Inserts the entry into the entry list sorted directories first then by file name.
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
void insert_sorted(std::list<fileman_entry>& entries, fileman_entry&& entry) {
auto it = std::lower_bound(
std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
@@ -146,9 +146,82 @@ namespace ui {
/* FileManBaseView ***********************************************************/
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
void FileManBaseView::load_directory_contents_unordered(const fs::path& dir_path, size_t file_cnt) {
current_path = dir_path;
entry_list.clear();
menu_view.clear();
auto filtering = !extension_filter.empty();
bool cxx_file = path_iequal(cxx_ext, extension_filter);
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
nb_pages = 1 + (file_cnt / items_per_page);
size_t start = pagination * items_per_page;
size_t stop = start + items_per_page;
if (file_cnt < stop) stop = file_cnt;
if (start > file_cnt) start = 0; // shouldn't hapen but check against it won't hurt
size_t curr = 0;
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
if (entry_list.size() >= items_per_page) {
break;
}
// Hide files starting with '.' (hidden / tmp).
if (!show_hidden_files && is_hidden_file(entry.path()))
continue;
if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path()))) {
curr++;
if (curr >= start) insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
}
} else if (fs::is_directory(entry.status())) {
curr++;
if (curr >= start) insert_sorted(entry_list, {entry.path().string(), 0, true});
}
}
// Add "parent" directory if not at the root.
if (!dir_path.empty() && pagination == 0)
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
// add next page
if (file_cnt > start + items_per_page) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
// add prev page
if (pagination > 0) {
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
}
}
int FileManBaseView::file_count_filtered(const fs::path& directory) {
int count{0};
auto filtering = !extension_filter.empty();
bool cxx_file = path_iequal(cxx_ext, extension_filter);
for (auto& entry : std::filesystem::directory_iterator(directory, (const TCHAR*)u"*")) {
if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
++count;
} else
++count;
}
return count;
}
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
size_t file_cnt = file_count_filtered(dir_path);
if (file_cnt >= max_items_loaded) {
load_directory_contents_unordered(dir_path, file_cnt);
return;
}
current_path = dir_path;
entry_list.clear();
menu_view.clear();
auto filtering = !extension_filter.empty();
bool cxx_file = path_iequal(cxx_ext, extension_filter);
@@ -161,19 +234,41 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path(), 0, true});
insert_sorted(entry_list, {entry.path().string(), 0, true});
}
}
// paginating
auto list_size = entry_list.size();
nb_pages = 1 + (list_size / items_per_page);
size_t start = pagination * items_per_page;
size_t stop = start + items_per_page;
if (list_size > start) {
if (list_size < stop)
stop = list_size;
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
}
// Add "parent" directory if not at the root.
if (!dir_path.empty())
entry_list.insert(entry_list.begin(), {parent_dir_path, 0, true});
if (!dir_path.empty() && pagination == 0)
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
// add next page
if (list_size > start + items_per_page) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
// add prev page
if (pagination > 0) {
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
}
}
fs::path FileManBaseView::get_selected_full_path() const {
if (get_selected_entry().path == parent_dir_path)
if (get_selected_entry().path == parent_dir_path.string())
return current_path.parent_path();
return current_path / get_selected_entry().path;
@@ -181,7 +276,10 @@ fs::path FileManBaseView::get_selected_full_path() const {
const fileman_entry& FileManBaseView::get_selected_entry() const {
// TODO: return reference to an "empty" entry on OOB?
return entry_list[menu_view.highlighted_index()];
auto it = entry_list.begin();
if (menu_view.highlighted_index() >= 1) std::advance(it, menu_view.highlighted_index());
return *it;
// return entry_list[menu_view.highlighted_index()];
}
FileManBaseView::FileManBaseView(
@@ -230,7 +328,7 @@ void FileManBaseView::push_dir(const fs::path& path) {
current_path /= path;
saved_index_stack.push_back(menu_view.highlighted_index());
menu_view.set_highlighted(0);
reload_current();
reload_current(true);
}
}
@@ -239,29 +337,56 @@ void FileManBaseView::pop_dir() {
return;
current_path = current_path.parent_path();
reload_current();
reload_current(true);
menu_view.set_highlighted(saved_index_stack.back());
saved_index_stack.pop_back();
}
std::string get_extension(std::string t) {
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return {};
} else {
return t.substr(index);
}
}
std::string get_stem(std::string t) {
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return t;
} else {
return t.substr(0, index);
}
}
std::string get_filename(std::string _s) {
const auto index = _s.find_last_of("/");
if (index == _s.npos) {
return _s;
} else {
return _s.substr(index + 1);
}
}
void FileManBaseView::refresh_list() {
if (on_refresh_widgets)
on_refresh_widgets(false);
auto prev_highlight = menu_view.highlighted_index();
prev_highlight = menu_view.highlighted_index();
menu_view.clear();
for (const auto& entry : entry_list) {
auto entry_name = truncate(entry.path, 20);
auto entry_name = std::string{entry.path.length() <= 20 ? entry.path : entry.path.substr(0, 20)};
if (entry.is_directory) {
auto size_str =
(entry.path == parent_dir_path)
? ""
: to_string_dec_uint(file_count(current_path / entry.path));
std::string size_str{};
if (entry.path == str_next || entry.path == str_back) {
size_str = to_string_dec_uint(1 + entry.size) + "/" + to_string_dec_uint(nb_pages); // show computed number of pages
} else {
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
}
menu_view.add_item(
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
ui::Color::yellow(),
&bitmap_icon_dir,
[this](KeyEvent key) {
@@ -270,11 +395,11 @@ void FileManBaseView::refresh_list() {
}});
} else {
const auto& assoc = get_assoc(entry.path.extension());
const auto& assoc = get_assoc(get_extension(entry.path));
auto size_str = to_string_file_size(entry.size);
menu_view.add_item(
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
assoc.color,
assoc.icon,
[this](KeyEvent key) {
@@ -282,16 +407,13 @@ void FileManBaseView::refresh_list() {
on_select_entry(key);
}});
}
// HACK: Should page menu items instead of limiting the number.
if (menu_view.item_count() >= max_items_shown)
break;
}
menu_view.set_highlighted(prev_highlight);
}
void FileManBaseView::reload_current() {
void FileManBaseView::reload_current(bool reset_pagination) {
if (reset_pagination) pagination = 0;
load_directory_contents(current_path);
refresh_list();
}
@@ -327,6 +449,21 @@ FileLoadView::FileLoadView(
on_select_entry = [this](KeyEvent) {
if (get_selected_entry().is_directory) {
if (get_selected_entry().path == str_full) {
return;
}
if (get_selected_entry().path == str_back) {
pagination--;
menu_view.set_highlighted(0);
reload_current(false);
return;
}
if (get_selected_entry().path == str_next) {
pagination++;
menu_view.set_highlighted(0);
reload_current(false);
return;
}
push_dir(get_selected_entry().path);
} else {
if (on_changed)
@@ -416,10 +553,10 @@ void FileManagerView::on_rename(std::string_view hint) {
auto& entry = get_selected_entry();
// Append the hint to the filename stem as a rename suggestion.
name_buffer = entry.path.stem().string();
name_buffer = get_stem(entry.path);
if (!hint.empty())
name_buffer += "_" + std::string{hint};
name_buffer += entry.path.extension().string();
name_buffer += get_extension(entry.path);
// Set the rename cursor to before the extension to make renaming simpler.
uint32_t cursor_pos = (uint32_t)name_buffer.length();
@@ -440,11 +577,11 @@ void FileManagerView::on_rename(std::string_view hint) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
reload_current(false);
});
if (!has_partner)
reload_current();
reload_current(false);
});
}
@@ -454,7 +591,7 @@ void FileManagerView::on_delete() {
return;
}
auto name = get_selected_entry().path.filename().string();
auto name = get_filename(get_selected_entry().path);
nav_.push<ModalMessageView>(
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
@@ -466,11 +603,11 @@ void FileManagerView::on_delete() {
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
reload_current(true);
});
if (!has_partner)
reload_current();
reload_current(true);
}
});
}
@@ -495,7 +632,7 @@ void FileManagerView::on_clean() {
std::filesystem::path current_full_path = path_name / file_name;
delete_file(current_full_path);
}
reload_current();
reload_current(true);
}
});
}
@@ -504,7 +641,7 @@ void FileManagerView::on_new_dir() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
make_new_directory(current_path / dir_name);
reload_current();
reload_current(true);
});
}
@@ -528,14 +665,14 @@ void FileManagerView::on_paste() {
clipboard_path = fs::path{};
clipboard_mode = ClipboardMode::None;
menu_view.focus();
reload_current();
reload_current(true);
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
reload_current(true);
});
}
@@ -558,7 +695,7 @@ bool FileManagerView::handle_file_open() {
return true;
} else if (path_iequal(bmp_ext, ext)) {
nav_.push<SplashViewer>(path);
reload_current();
reload_current(false);
return true;
} else if (path_iequal(rem_ext, ext)) {
nav_.push<RemoteView>(path);
@@ -570,7 +707,7 @@ bool FileManagerView::handle_file_open() {
bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path;
get_selected_entry().path != parent_dir_path.string();
}
FileManagerView::FileManagerView(
@@ -602,7 +739,7 @@ FileManagerView::FileManagerView(
});
menu_view.on_highlight = [this]() {
if (menu_view.highlighted_index() >= max_items_shown - 1) {
if (menu_view.highlighted_index() >= max_items_loaded - 1) { // todo check this if correct
text_date.set_style(&Styles::red);
text_date.set("Too many files!");
} else {
@@ -617,13 +754,30 @@ FileManagerView::FileManagerView(
refresh_list();
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else if (key == KeyEvent::Select && handle_file_open()) {
return;
} else {
button_rename.focus();
if (key == KeyEvent::Select) {
if (get_selected_entry().is_directory) {
if (get_selected_entry().path == str_full) {
return;
}
if (get_selected_entry().path == str_back) {
pagination--;
menu_view.set_highlighted(0);
reload_current(false);
return;
}
if (get_selected_entry().path == str_next) {
pagination++;
menu_view.set_highlighted(0);
reload_current(false);
return;
}
push_dir(get_selected_entry().path);
return;
} else if (handle_file_open()) {
return;
}
}
button_rename.focus();
};
button_rename.on_select = [this]() {
+14 -5
View File
@@ -31,7 +31,7 @@
namespace ui {
struct fileman_entry {
std::filesystem::path path{};
std::string path{};
uint32_t size{};
bool is_directory{};
};
@@ -61,8 +61,12 @@ class FileManBaseView : public View {
void push_dir(const std::filesystem::path& path);
protected:
static constexpr size_t max_filename_length = 64;
static constexpr size_t max_items_shown = 100;
uint32_t prev_highlight = 0;
uint8_t pagination = 0;
uint8_t nb_pages = 1;
static constexpr size_t max_filename_length = 20;
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
static constexpr size_t items_per_page = 20;
struct file_assoc_t {
std::filesystem::path extension;
@@ -85,10 +89,12 @@ class FileManBaseView : public View {
std::filesystem::path get_selected_full_path() const;
const fileman_entry& get_selected_entry() const;
int file_count_filtered(const std::filesystem::path& directory);
void pop_dir();
void refresh_list();
void reload_current();
void reload_current(bool reset_pagination = false);
void load_directory_contents(const std::filesystem::path& dir_path);
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
NavigationView& nav_;
@@ -97,11 +103,14 @@ class FileManBaseView : public View {
std::function<void(KeyEvent)> on_select_entry{nullptr};
std::function<void(bool)> on_refresh_widgets{nullptr};
const std::string str_back{"<--"};
const std::string str_next{"-->"};
const std::string str_full{"Can't load more.."};
const std::filesystem::path parent_dir_path{u".."};
std::filesystem::path current_path{u""};
std::filesystem::path extension_filter{u""};
std::vector<fileman_entry> entry_list{};
std::list<fileman_entry> entry_list{};
std::vector<uint32_t> saved_index_stack{};
bool show_hidden_files{false};
@@ -50,12 +50,12 @@ int32_t GlassView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t
void GlassView::update_display_beep() {
if (beep_enabled) {
button_beep_squelch.set_style(&Styles::green);
// <bip:-XXXdb>
button_beep_squelch.set_text("[bip>" + to_string_dec_int(beep_squelch, 4) + "db]");
// bip-XXdb
button_beep_squelch.set_text("bip" + to_string_dec_int(beep_squelch, 3) + "db");
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
} else {
button_beep_squelch.set_style(&Styles::white);
button_beep_squelch.set_text("[ beep OFF ]");
button_beep_squelch.set_text("bip OFF ");
}
}
@@ -548,8 +548,8 @@ GlassView::GlassView(
button_beep_squelch.on_change = [this]() {
int new_beep_squelch = beep_squelch + button_beep_squelch.get_encoder_delta();
if (new_beep_squelch < -100)
new_beep_squelch = -100;
if (new_beep_squelch < -99)
new_beep_squelch = -99;
if (new_beep_squelch > 20)
new_beep_squelch = 20;
beep_squelch = new_beep_squelch;
@@ -173,7 +173,7 @@ class GlassView : public View {
Labels labels{
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Color::light_grey()},
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
{{0, 2 * 16}, "PRESET:", Color::light_grey()},
{{0, 2 * 16}, "P:", Color::light_grey()},
{{0, 3 * 16}, "MARKER: MHz RXIQCAL", Color::light_grey()},
//{{0, 4 * 16}, "RES: STEPS:", Color::light_grey()}};
{{0, 4 * 16}, "RES: VOL:", Color::light_grey()}};
@@ -215,12 +215,12 @@ class GlassView : public View {
{28 * 8, 1 * 16}};
OptionsField range_presets{
{7 * 8, 2 * 16},
10,
{2 * 8, 2 * 16},
20,
{}};
ButtonWithEncoder button_beep_squelch{
{18 * 8, 2 * 16 + 4, 12 * 8, 1 * 8},
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
""};
TextField field_marker{
+17 -15
View File
@@ -523,21 +523,6 @@ MicTXView::MicTXView(
};
check_mic_to_HP.set_value(mic_to_HP_enabled);
check_rxactive.on_select = [this](Checkbox&, bool v) {
// vumeter.set_value(0); //Start with a clean vumeter
rx_enabled = v;
check_mic_to_HP.hidden(rx_enabled); // Toggle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
if ((rx_enabled) && (transmitting))
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
if (rx_enabled)
check_va.set_value(false); // Disallow voice activation during RX audio (for now) - Future TODO: Should allow VOX during RX audio
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
set_dirty(); // Refresh interface
};
check_rxactive.set_value(rx_enabled);
tx_button.on_select = [this](Button&) {
if (!transmitting) {
set_tx(true);
@@ -590,12 +575,28 @@ MicTXView::MicTXView(
// Trigger receiver to update modulation.
if (rx_enabled)
receiver_model.set_squelch_level(receiver_model.squelch_level());
check_rxactive.on_select = [this](Checkbox&, bool v) {
// vumeter.set_value(0); //Start with a clean vumeter
rx_enabled = v;
check_mic_to_HP.hidden(rx_enabled); // Toggle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
if ((rx_enabled) && (transmitting))
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
if (rx_enabled)
check_va.set_value(false); // Disallow voice activation during RX audio (for now) - Future TODO: Should allow VOX during RX audio
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
set_dirty(); // Refresh interface
};
check_rxactive.set_value(rx_enabled);
}
MicTXView::MicTXView(
NavigationView& nav,
ReceiverModel::settings_t override)
: MicTXView(nav) {
// Settings to override when launched from another app (versus from AppSettings .ini file)
// Try to use the modulation/bandwidth from RX settings.
// TODO: These concepts should be merged so there's only one.
switch (override.mode) {
@@ -616,6 +617,7 @@ MicTXView::MicTXView(
break;
}
field_frequency.set_value(override.frequency_app_override);
check_common_freq_tx_rx.set_value(true); // freq passed from other app is in tx_frequency, so set rx_frequency=tx_frequency
// TODO: bandwidth selection is tied too tightly to the UI
-1
View File
@@ -118,7 +118,6 @@ class MicTXView : public View {
app_settings::SettingsManager settings_{
"tx_mic",
app_settings::Mode::RX_TX,
app_settings::Options::UseGlobalTargetFrequency,
{
{"mic_mod_index"sv, &mic_mod_index},
{"rxbw_index"sv, &rxbw_index},
@@ -42,6 +42,32 @@ POCSAGTXView::~POCSAGTXView() {
baseband::shutdown();
}
void POCSAGTXView::on_remote(const PocsagTosendMessage data) {
// check if still sending or not
size_t tmp = 0;
if (data.baud == 1200) tmp = 1;
if (data.baud == 2400) tmp = 2;
options_bitrate.set_selected_index(tmp);
tmp = 0;
options_type.set_selected_index(data.type);
if (data.function == 'B') tmp = 1;
if (data.function == 'C') tmp = 2;
if (data.function == 'D') tmp = 3;
options_function.set_selected_index(tmp);
options_phase.set_selected_index(data.phase == 'P' ? 0 : 1);
field_address.set_value(data.addr);
message = (char*)data.msg;
text_message.set(message);
options_bitrate.dirty();
options_type.dirty();
options_function.dirty();
options_phase.dirty();
field_address.dirty();
text_message.dirty();
tx_view.focus();
start_tx();
}
void POCSAGTXView::on_tx_progress(const uint32_t progress, const bool done) {
if (done) {
transmitter_model.disable();
@@ -76,6 +76,7 @@ class POCSAGTXView : public View {
void on_set_text(NavigationView& nav);
void on_tx_progress(const uint32_t progress, const bool done);
void on_remote(const PocsagTosendMessage data);
bool start_tx();
Labels labels{
@@ -142,6 +143,13 @@ class POCSAGTXView : public View {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.progress, message.done);
}};
MessageHandlerRegistration message_handler_tx_remote{
Message::ID::PocsagTosend,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const PocsagTosendMessage*>(p);
this->on_remote(message);
}};
};
} /* namespace ui */
+2 -2
View File
@@ -508,7 +508,7 @@ ReconView::ReconView(NavigationView& nav)
auto settings = receiver_model.settings();
settings.frequency_step = step_mode.selected_index_value();
if (field_mode.selected_index_value() == SPEC_MODULATION)
nav_.replace<CaptureAppView>();
nav_.replace<CaptureAppView>(settings);
else
nav_.replace<AnalogAudioView>(settings);
};
@@ -539,7 +539,7 @@ ReconView::ReconView(NavigationView& nav)
}
}
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
// MicTX wants Frequency, Modulation and Bandwidth overrides, but that's only stored on the RX model.
nav_.replace<MicTXView>(receiver_model.settings());
};
@@ -59,7 +59,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
if (new_file_path.native().find(freqman_dir.native()) == 0) {
if (new_file_path.native().find((u"/" / freqman_dir).native()) == 0) {
_input_file = new_file_path.stem().string();
text_input_file.set(_input_file);
} else {
@@ -72,7 +72,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
if (new_file_path.native().find(freqman_dir.native()) == 0) {
if (new_file_path.native().find((u"/" / freqman_dir).native()) == 0) {
_output_file = new_file_path.stem().string();
button_choose_output_name.set_text(_output_file);
} else {
+2 -2
View File
@@ -338,7 +338,7 @@ ScannerView::ScannerView(
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
if (new_file_path.native().find(freqman_dir.native()) == 0) {
if (new_file_path.native().find((u"/" / freqman_dir).native()) == 0) {
scan_pause();
frequency_file_load(new_file_path);
} else {
@@ -413,7 +413,7 @@ ScannerView::ScannerView(
button_mic_app.on_select = [this](Button&) {
if (scan_thread)
scan_thread->stop();
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
// MicTX wants Frequency, Modulation and Bandwidth overrides, but that's only stored on the RX model.
nav_.replace<MicTXView>(receiver_model.settings());
};
+56
View File
@@ -31,6 +31,7 @@
#include "ui_receiver.hpp"
#include "ui_touch_calibration.hpp"
#include "ui_text_editor.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "portapack_persistent_memory.hpp"
#include "lpc43xx_cpp.hpp"
@@ -839,6 +840,60 @@ void SetMenuColorView::focus() {
button_save.focus();
}
/* SetAutoStartView ************************************/
SetAutostartView::SetAutostartView(NavigationView& nav) {
add_children({&labels,
&button_save,
&button_cancel,
&options});
button_save.on_select = [&nav, this](Button&) {
autostart_app = "";
if (selected != 0) {
auto it = full_app_list.find(selected);
if (it != full_app_list.end())
autostart_app = it->second;
}
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
// options
i = 0;
OptionsField::option_t o{"-none-", i};
opts.emplace_back(o);
for (auto& app : NavigationView::appList) {
if (app.id == nullptr) continue;
i++;
o = {app.displayName, i};
opts.emplace_back(o);
full_app_list.emplace(i, app.id);
if (autostart_app == app.id) selected = i;
}
ExternalItemsMenuLoader::load_all_external_items_callback([this](ui::AppInfoConsole& app) {
if (app.appCallName == nullptr) return;
i++;
OptionsField::option_t o = {app.appFriendlyName, i};
opts.emplace_back(o);
full_app_list.emplace(i, app.appCallName);
if (autostart_app == app.appCallName) selected = i;
});
options.set_options(opts);
options.on_change = [this](size_t, OptionsField::value_t v) {
selected = v;
};
options.set_selected_index(selected);
}
void SetAutostartView::focus() {
options.focus();
}
/* SettingsMenuView **************************************/
SettingsMenuView::SettingsMenuView(NavigationView& nav)
@@ -866,6 +921,7 @@ void SettingsMenuView::on_populate() {
//{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [this]() { nav_.push<SetQRCodeView>(); }},
{"Brightness", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetFakeBrightnessView>(); }},
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
{"Autostart", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetAutostartView>(); }},
});
}
+36
View File
@@ -799,6 +799,42 @@ class SetMenuColorView : public View {
};
};
class SetAutostartView : public View {
public:
SetAutostartView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Autostart"; };
private:
int32_t i = 0;
std::string autostart_app{""};
OptionsField::options_t opts{};
std::map<int32_t, std::string> full_app_list{}; // looking table
int32_t selected = 0;
SettingsStore nav_setting{
"nav"sv,
{{"autostart_app"sv, &autostart_app}}};
Labels labels{
{{1 * 8, 1 * 16}, "Select app to start on boot", Color::light_grey()},
{{2 * 8, 2 * 16}, "(an SD Card is required)", Color::light_grey()}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
OptionsField options{
{8 * 8, 4 * 16},
30,
{}};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SettingsMenuView : public BtnGridView {
public:
SettingsMenuView(NavigationView& nav);
+1 -1
View File
@@ -81,7 +81,7 @@ SubGhzDView::SubGhzDView(NavigationView& nav)
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_step(100000);
field_frequency.set_step(10000);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
@@ -105,7 +105,7 @@ WeatherView::WeatherView(NavigationView& nav)
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_step(100000);
field_frequency.set_step(10000);
options_temperature.on_change = [this](size_t, int32_t i) {
weather_units_fahr = (bool)i;
+1
View File
@@ -158,6 +158,7 @@ static void event_loop() {
event_dispatcher.set_display_sleep(true);
}};
portapack::setEventDispatcherToUSBSerial(&event_dispatcher);
system_view.get_navigation_view()->handle_autostart();
event_dispatcher.run();
}
+1
View File
@@ -68,6 +68,7 @@ rf::Frequency ReceiverModel::target_frequency() const {
void ReceiverModel::set_target_frequency(rf::Frequency f) {
persistent_memory::set_target_frequency(f);
settings_.frequency_app_override = f;
update_tuning_frequency();
}
+1
View File
@@ -48,6 +48,7 @@ class ReceiverModel {
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
uint32_t sampling_rate = 3'072'000;
rf::Frequency frequency_step = 25'000;
rf::Frequency frequency_app_override = 0;
uint8_t lna_gain_db = 32;
uint8_t vga_gain_db = 32;
bool rf_amp = false;
+5 -1
View File
@@ -251,8 +251,12 @@ bool MenuView::on_key(const KeyEvent key) {
case KeyEvent::Down:
return set_highlighted(highlighted_item + 1);
case KeyEvent::Select:
case KeyEvent::Right:
if (on_right) {
on_right();
}
[[fallthrough]];
case KeyEvent::Select:
if (menu_items[highlighted_item].on_select) {
menu_items[highlighted_item].on_select(key);
}
+1
View File
@@ -75,6 +75,7 @@ class MenuItemView : public Widget {
class MenuView : public View {
public:
std::function<void(void)> on_left{};
std::function<void(void)> on_right{};
std::function<void(void)> on_highlight{nullptr};
MenuView(Rect new_parent_rect = {0, 0, screen_width, screen_height - 16},
+21
View File
@@ -107,6 +107,10 @@
#include "file_reader.hpp"
#include "png_writer.hpp"
#include "file_path.hpp"
#include "ff.h"
#include <locale>
#include <codecvt>
using portapack::receiver_model;
using portapack::transmitter_model;
@@ -708,6 +712,23 @@ bool NavigationView::set_on_pop(std::function<void()> on_pop) {
return true;
}
void NavigationView::handle_autostart() {
std::string autostart_app{""};
SettingsStore nav_setting{
"nav"sv,
{{"autostart_app"sv, &autostart_app}}};
if (!autostart_app.empty()) {
if (StartAppByName(autostart_app.c_str())) return;
// if returned false, check for external apps by that name, and try to start it
std::string appwithpath = "/" + apps_dir.string() + "/";
appwithpath += autostart_app;
appwithpath += ".ppma";
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
std::filesystem::path pth = conv.from_bytes(appwithpath.c_str());
ui::ExternalItemsMenuLoader::run_external_app(*this, pth);
}
}
/* Helpers **************************************************************/
static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
+2
View File
@@ -136,6 +136,8 @@ class NavigationView : public View {
static const AppList appList;
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
void handle_autostart();
private:
struct ViewState {
std::unique_ptr<View> view;
+14 -8
View File
@@ -75,6 +75,7 @@ RecordView::RecordView(
&rect_background,
//&button_pitch_rssi,
&button_record,
&gps_icon,
&text_record_filename,
&text_record_dropped,
&text_time_available,
@@ -93,6 +94,7 @@ RecordView::RecordView(
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
this->on_tick_second();
};
gps_icon.hidden(true);
}
RecordView::~RecordView() {
@@ -180,6 +182,13 @@ void RecordView::start() {
}
std::filesystem::path base_path;
auto tmp_path = filename_stem_pattern; // store it, to be able to modify without causing permanent change
// check for geo data, if present append filename with _GEO
if (latitude != 0 && longitude != 0 && latitude < 200 && longitude < 200) {
tmp_path.append_filename(u"_GEO");
}
if (filename_date_frequency) {
rtc_time::now(datetime);
@@ -192,24 +201,19 @@ void RecordView::start() {
to_string_dec_uint(datetime.minute()) +
to_string_dec_uint(datetime.second());
base_path = filename_stem_pattern.string() + "_" + date_time + "_" +
base_path = tmp_path.string() + "_" + date_time + "_" +
trim(to_string_freq(receiver_model.target_frequency())) + "Hz";
base_path = folder / base_path;
} else if (filename_as_is) {
base_path = filename_stem_pattern.string();
base_path = tmp_path.string();
base_path = folder / base_path;
} else
base_path = next_filename_matching_pattern(folder / filename_stem_pattern);
base_path = next_filename_matching_pattern(folder / tmp_path);
if (base_path.empty()) {
return;
}
// check for geo data, if present append filename with _GEO
if (latitude != 0 && longitude != 0 && latitude < 200 && longitude < 200) {
base_path.append_filename(u"_GEO");
}
std::unique_ptr<stream::Writer> writer;
switch (file_type) {
case FileType::WAV: {
@@ -348,6 +352,8 @@ void RecordView::trim_capture() {
}
void RecordView::on_gps(const GPSPosDataMessage* msg) {
if (msg->lat == 0 || msg->lat > 399) return; // not valid one
if (latitude == 0) gps_icon.hidden(false); // prev was 0, so not shown already
latitude = msg->lat;
longitude = msg->lon;
satinuse = msg->satinuse;
+6
View File
@@ -144,6 +144,12 @@ class RecordView : public View {
"",
};
Image gps_icon{
{2 * 8 + 1, 0 * 16, 2 * 8, 1 * 16},
&bitmap_target,
Color::white(),
Color::black()};
std::unique_ptr<CaptureThread> capture_thread{};
MessageHandlerRegistration message_handler_capture_thread_error{
+79
View File
@@ -1053,6 +1053,84 @@ static void cmd_settingsreset(BaseSequentialStream* chp, int argc, char* argv[])
chprintf(chp, "ok\r\n");
}
static void cmd_sendpocsag(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: sendpocsag <addr> <msglen> [baud] [type] [function] [phase] \r\n";
(void)argv;
if (argc < 2) {
chprintf(chp, usage);
return;
}
uint64_t addr = atol(argv[0]);
int msglen = atoi(argv[1]); // without minimum limit, since addr only don't send anything
if (msglen > 30 || msglen < 0) {
chprintf(chp, "error, msglen max is 30\r\n");
return;
}
int baud = 1200;
if (argc >= 3) {
baud = atoi(argv[2]);
if (baud != 512 && baud != 1200 && baud != 2400) {
chprintf(chp, "error, baud can only be 512, 1200 or 2400\r\n");
return;
}
}
int type = 2;
if (argc >= 4) {
type = atoi(argv[3]);
if (type > 2 || type < 0) {
chprintf(chp, "error, type can be 0 (ADDRESS_ONLY) 1 (NUMERIC_ONLY) 2 (ALPHANUMERIC)\r\n");
return;
}
}
char function = 'D';
if (argc >= 5) {
function = *argv[4];
if (function < 'A' && function > 'D') {
chprintf(chp, "error, function can be A, B, C or D\r\n");
return;
}
}
char phase = 'P';
if (argc >= 6) {
phase = *argv[5];
if (phase != 'P' && phase != 'N') {
chprintf(chp, "error, phase can be P or N\r\n");
return;
}
}
uint8_t msg[31] = {0};
if (msglen > 0) {
chprintf(chp, "send %d bytes\r\n", msglen);
do {
size_t bytes_to_read = msglen > USB_BULK_BUFFER_SIZE ? USB_BULK_BUFFER_SIZE : msglen;
size_t bytes_read = chSequentialStreamRead(chp, &msg[0], bytes_to_read);
if (bytes_read != bytes_to_read)
return;
msglen -= bytes_read;
} while (msglen > 0);
}
auto evtd = getEventDispatcherInstance();
if (!evtd) return;
auto top_widget = evtd->getTopWidget();
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
if (!nav->StartAppByName("pocsagtx")) {
chprintf(chp, "error starting pocsagtx\r\n");
return;
}
chThdSleepMilliseconds(1000); // wait for app to start
PocsagTosendMessage message{(uint16_t)baud, (uint8_t)type, function, phase, (uint8_t)msglen, msg, addr};
EventDispatcher::send_message(message);
chprintf(chp, "ok\r\n");
}
static const ShellCommand commands[] = {
{"reboot", cmd_reboot},
{"dfu", cmd_dfu},
@@ -1083,6 +1161,7 @@ static const ShellCommand commands[] = {
{"radioinfo", cmd_radioinfo},
{"pmemreset", cmd_pmemreset},
{"settingsreset", cmd_settingsreset},
{"sendpocsag", cmd_sendpocsag},
{NULL, NULL}};
static const ShellConfig shell_cfg1 = {
+4
View File
@@ -46,6 +46,10 @@ class ADSBFrame {
return (raw_data[4] & 7);
}
uint8_t get_sil_value() {
return ((raw_data[10] >> 6) & 0b11); // 83-84 bits
}
uint32_t get_ICAO_address() {
return (raw_data[1] << 16) + (raw_data[2] << 8) + raw_data[3];
}
+30
View File
@@ -122,6 +122,7 @@ class Message {
OrientationData = 64,
EnvironmentData = 65,
AudioBeep = 66,
PocsagTosend = 67,
MAX
};
@@ -1379,4 +1380,33 @@ class AudioBeepMessage : public Message {
uint32_t sample_rate = 24000;
uint32_t duration_ms = 100;
};
class PocsagTosendMessage : public Message {
public:
constexpr PocsagTosendMessage(
uint16_t baud = 1200,
uint8_t type = 2,
char function = 'D',
char phase = 'N',
uint8_t msglen = 0,
uint8_t msg[31] = {0},
uint64_t addr = 0)
: Message{ID::PocsagTosend},
baud{baud},
type{type},
function{function},
phase{phase},
msglen{msglen},
addr{addr} {
memcpy(this->msg, msg, 31);
}
uint16_t baud = 1200;
uint8_t type = 2;
char function = 'D';
char phase = 'N';
uint8_t msglen = 0;
uint8_t msg[31] = {0};
uint64_t addr = 0;
};
#endif /*__MESSAGE_H__*/
+1 -1
View File
@@ -1,5 +1,5 @@
# Common SSTV frequencies
# frequencies marked Active have more frequent transmissions.
# 'Active' => most used
f=3640000,d=SSTV 80M
f=7043000,d=SSTV 40M - Active
f=7170000,d=SSTV 40M - Active
+4 -1
View File
@@ -16,11 +16,14 @@
76,95,FM BROADCAST JAPAN
65,74,FM BROADCAST RUSSIA
# CITIZENS BAND RADIO
26,28,CITIZENS BAND RADIO
26,28,CB RADIO
# COMMON PUBLIC SERVICE BANDS
150,160, PUBLIC SERVICE 155
450,470,PUBLIC SERVICE 460
769,775,PUBLIC SERVICE 770
# CubeSats
144,146,145 CubeSats
435,438,436 CubeSats
# DECT
1879,1931,DECT
# FRS/GMRS