mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 19:13:00 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb1b703dc1 | |||
| 990f63e5de | |||
| 6fe8ab4f99 | |||
| d6ed12191d | |||
| 8b159f447a | |||
| e9c32504d1 | |||
| 004799e1a3 | |||
| 8e90c65a62 | |||
| bfe67a13b7 | |||
| f091db3270 | |||
| a602abf1d8 | |||
| 689224fd8d | |||
| 23cf1c2737 | |||
| c48cbb7e55 | |||
| 3665b3c607 | |||
| 804b7c87b7 | |||
| a30875b582 | |||
| 56303eb385 | |||
| d29172f41e | |||
| 6d9d5ad1af | |||
| 2acefee4d2 |
@@ -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 @@
|
||||
v1.9.1
|
||||
v2.0.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.0.0
|
||||
v2.0.1
|
||||
|
||||
Vendored
+148
@@ -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
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()},
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
|
||||
@@ -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>(); }},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user