mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 02:52:58 +00:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53b59a8a22 | |||
| 100bea644c | |||
| fe71592b68 | |||
| 4a96feb829 | |||
| e666eb1797 | |||
| 963c6e5238 | |||
| fac71677d4 | |||
| 075b23b291 | |||
| 3628576d60 | |||
| a3b71d0e3d | |||
| 0ea8453e8a | |||
| 1ffedace7b | |||
| e43f7a7980 | |||
| f6a2d21624 | |||
| f572b00391 | |||
| d74fd527e2 | |||
| 819f35ac93 | |||
| 60da661999 | |||
| 43077c79cf | |||
| 06651dc97c | |||
| 67975d76a0 | |||
| 282e4da1cb | |||
| e7359563c6 | |||
| 03ca87b13e | |||
| b2da21b88a | |||
| bb1b703dc1 | |||
| 990f63e5de | |||
| 6fe8ab4f99 | |||
| d6ed12191d | |||
| 8b159f447a | |||
| e9c32504d1 | |||
| 004799e1a3 | |||
| 8e90c65a62 | |||
| bfe67a13b7 | |||
| f091db3270 | |||
| a602abf1d8 | |||
| 689224fd8d | |||
| 23cf1c2737 | |||
| c48cbb7e55 | |||
| 3665b3c607 | |||
| 804b7c87b7 | |||
| a30875b582 | |||
| 56303eb385 | |||
| d29172f41e | |||
| 6d9d5ad1af | |||
| 2acefee4d2 | |||
| cb9fee8f08 | |||
| b473930f94 | |||
| 6e5eadd25c | |||
| 76017c91a9 | |||
| f1ebb1a6e3 | |||
| 746bf1c15f | |||
| 0db65ccb6b | |||
| fe2fbb847f | |||
| ba36680a7b | |||
| 90ff1e62ce | |||
| 2e1e02baac | |||
| 2dca408e45 | |||
| fa56cdd015 |
@@ -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
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
.dep/
|
||||
/build*
|
||||
CMakeFiles/
|
||||
cmake-build-debug/
|
||||
|
||||
# Debugging
|
||||
.gdbinit*
|
||||
|
||||
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
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -10,7 +10,7 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
|
||||
|
||||
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
|
||||
*[PortaPack H2+HackRF+battery](https://grabify.link/7T28JP) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
|
||||
|
||||
# What is this?
|
||||
|
||||
@@ -26,11 +26,11 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
|
||||
## What to buy?
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://grabify.link/7T28JP), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
|
||||
|
||||
: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: Some individuals lean towards the [H2 with a metal enclosure](https://grabify.link/HTDXG5), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
|
||||
|
||||
:heavy_check_mark: Another popular option is the clone of the [PortaPack H1](https://s.click.aliexpress.com/e/_Dkbqs2X).
|
||||
: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://grabify.link/LG0OUY), you can support them too by ordering.
|
||||
|
||||
: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:
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ endif()
|
||||
|
||||
add_subdirectory(application)
|
||||
add_subdirectory(baseband)
|
||||
add_subdirectory(standalone)
|
||||
add_subdirectory(test)
|
||||
|
||||
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
|
||||
@@ -88,7 +89,7 @@ add_custom_target(
|
||||
program-external-apps
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND ${PROJECT_SOURCE_DIR}/tools/copy_external_apps.sh
|
||||
DEPENDS program
|
||||
DEPENDS program standalone_apps
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
@@ -100,8 +101,9 @@ add_custom_command(
|
||||
COMMAND cp ${FIRMWARE_FILENAME} firmware_tar/FIRMWARE/portapack-mayhem_${VERSION_NOHASH}.bin
|
||||
COMMAND mkdir -p firmware_tar/APPS
|
||||
COMMAND cp application/*.ppma firmware_tar/APPS
|
||||
COMMAND cp standalone/*/*.ppmp firmware_tar/APPS
|
||||
COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
|
||||
DEPENDS firmware ${FIRMWARE_FILENAME}
|
||||
DEPENDS firmware ${FIRMWARE_FILENAME} standalone_apps
|
||||
# Dont use VERBATIM here as it prevents usage of globbing (*)
|
||||
# There shouldnt be any funny business in the filenames above :)
|
||||
)
|
||||
|
||||
@@ -176,7 +176,10 @@ set(CPPSRC
|
||||
${COMMON}/ui_language.cpp
|
||||
${COMMON}/utility.cpp
|
||||
${COMMON}/wm8731.cpp
|
||||
${COMMON}/ads1110.cpp
|
||||
${COMMON}/battery.cpp
|
||||
${COMMON}/performance_counter.cpp
|
||||
${COMMON}/bmpfile.cpp
|
||||
app_settings.cpp
|
||||
audio.cpp
|
||||
baseband_api.cpp
|
||||
@@ -209,6 +212,7 @@ set(CPPSRC
|
||||
usb_serial_thread.cpp
|
||||
usb_serial.cpp
|
||||
usb_serial_host_to_device.cpp
|
||||
usb_serial_asyncmsg.cpp
|
||||
qrcodegen.cpp
|
||||
radio.cpp
|
||||
receiver_model.cpp
|
||||
@@ -259,6 +263,7 @@ set(CPPSRC
|
||||
ui/ui_textentry.cpp
|
||||
ui/ui_tone_key.cpp
|
||||
ui/ui_transmitter.cpp
|
||||
ui/ui_bmpview.cpp
|
||||
apps/acars_app.cpp
|
||||
apps/ais_app.cpp
|
||||
apps/analog_audio_app.cpp
|
||||
@@ -273,14 +278,15 @@ set(CPPSRC
|
||||
apps/pocsag_app.cpp
|
||||
# apps/replay_app.cpp
|
||||
apps/soundboard_app.cpp
|
||||
apps/tpms_app.cpp
|
||||
apps/tpms_app.cpp
|
||||
# apps/tpms_app.cpp
|
||||
apps/ui_about_simple.cpp
|
||||
apps/ui_adsb_rx.cpp
|
||||
apps/ui_adsb_tx.cpp
|
||||
apps/ui_aprs_rx.cpp
|
||||
apps/ui_aprs_tx.cpp
|
||||
apps/ui_battinfo.cpp
|
||||
apps/ui_bht_tx.cpp
|
||||
apps/ui_bmp_file_viewer.cpp
|
||||
apps/ui_btle_rx.cpp
|
||||
# apps/ui_coasterp.cpp
|
||||
apps/ui_debug.cpp
|
||||
@@ -319,6 +325,7 @@ set(CPPSRC
|
||||
# apps/ui_spectrum_painter.cpp
|
||||
apps/ui_ss_viewer.cpp
|
||||
apps/ui_sstvtx.cpp
|
||||
apps/ui_standalone_view.cpp
|
||||
apps/ui_subghzd.cpp
|
||||
# apps/ui_test.cpp
|
||||
apps/ui_text_editor.cpp
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "ais_app.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "database.hpp"
|
||||
@@ -32,6 +33,8 @@ using namespace portapack;
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ais {
|
||||
namespace format {
|
||||
|
||||
@@ -190,6 +193,10 @@ void AISLogger::on_packet(const ais::Packet& packet) {
|
||||
}
|
||||
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
}
|
||||
|
||||
void AISRecentEntry::update(const ais::Packet& packet) {
|
||||
@@ -377,6 +384,7 @@ AISAppView::AISAppView(NavigationView& nav)
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&rssi,
|
||||
&field_volume,
|
||||
&channel,
|
||||
&recent_entries_view,
|
||||
&recent_entry_detail_view,
|
||||
@@ -402,9 +410,15 @@ AISAppView::AISAppView(NavigationView& nav)
|
||||
if (logger) {
|
||||
logger->append(logs_dir / u"AIS.TXT");
|
||||
}
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
}
|
||||
|
||||
AISAppView::~AISAppView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
@@ -209,6 +209,9 @@ class AISAppView : public View {
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
|
||||
@@ -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()},
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "ert_app.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
@@ -33,6 +33,8 @@ using namespace portapack;
|
||||
#include "string_format.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ert {
|
||||
|
||||
namespace format {
|
||||
@@ -121,6 +123,7 @@ ERTAppView::ERTAppView(NavigationView& nav)
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&rssi,
|
||||
&field_volume,
|
||||
&recent_entries_view,
|
||||
});
|
||||
|
||||
@@ -132,9 +135,15 @@ ERTAppView::ERTAppView(NavigationView& nav)
|
||||
if (logger) {
|
||||
logger->append(logs_dir / u"ERT.TXT");
|
||||
}
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
}
|
||||
|
||||
ERTAppView::~ERTAppView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -158,6 +167,10 @@ void ERTAppView::on_packet(const ert::Packet& packet) {
|
||||
entry.update(packet);
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
}
|
||||
|
||||
void ERTAppView::on_show_list() {
|
||||
|
||||
@@ -165,6 +165,9 @@ class ERTAppView : public View {
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::ERTPacket,
|
||||
[this](Message* const p) {
|
||||
|
||||
@@ -136,6 +136,10 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
||||
};
|
||||
|
||||
refresh_ui();
|
||||
|
||||
if (pmem::beep_on_packets())
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
|
||||
audio::output::start();
|
||||
receiver_model.enable();
|
||||
baseband::set_pocsag();
|
||||
@@ -304,6 +308,10 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
||||
|
||||
// Set status icon color to indicate state machine state.
|
||||
image_status.set_foreground(get_status_color(pocsag_state));
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
}
|
||||
|
||||
void POCSAGAppView::on_stats(const POCSAGStatsMessage* stats) {
|
||||
|
||||
@@ -1,83 +1,88 @@
|
||||
#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 last text line to make it more obvious that list is scrollable
|
||||
menu_view.set_highlighted(10);
|
||||
}
|
||||
|
||||
} /* 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, 264},
|
||||
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
|
||||
|
||||
|
||||
@@ -32,9 +32,12 @@
|
||||
#include "rtc_time.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
static std::string get_map_tag(const AircraftRecentEntry& entry) {
|
||||
@@ -104,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);
|
||||
}
|
||||
|
||||
@@ -351,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));
|
||||
@@ -373,7 +378,8 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
||||
&rssi,
|
||||
&recent_entries_view,
|
||||
&status_frame,
|
||||
&status_good_frame});
|
||||
&status_good_frame,
|
||||
&field_volume});
|
||||
|
||||
recent_entries_view.set_parent_rect({0, 16, 240, 272});
|
||||
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
|
||||
@@ -395,10 +401,16 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
|
||||
|
||||
receiver_model.enable();
|
||||
baseband::set_adsb();
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
}
|
||||
|
||||
ADSBRxView::~ADSBRxView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -461,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));
|
||||
}
|
||||
|
||||
@@ -469,10 +480,16 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
logger->log(log_entry);
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBRxView::on_tick_second() {
|
||||
|
||||
@@ -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.
|
||||
@@ -421,19 +426,22 @@ class ADSBRxView : public View {
|
||||
{18 * 8, 0 * 16}};
|
||||
|
||||
RSSI rssi{
|
||||
{20 * 8, 4, 10 * 7, 8},
|
||||
{20 * 8, 4, 7 * 8, 8},
|
||||
};
|
||||
|
||||
ActivityDot status_frame{
|
||||
{screen_width - 3, 5, 2, 2},
|
||||
{27 * 8 + 2, 5, 2, 2},
|
||||
Color::white(),
|
||||
};
|
||||
|
||||
ActivityDot status_good_frame{
|
||||
{screen_width - 3, 9, 2, 2},
|
||||
{27 * 8 + 2, 9, 2, 2},
|
||||
Color::green(),
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame{
|
||||
Message::ID::ADSBFrame,
|
||||
[this](Message* const p) {
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_battinfo.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool BattinfoView::needRun = true;
|
||||
void BattinfoView::focus() {
|
||||
button_exit.focus();
|
||||
}
|
||||
|
||||
// called each 1/60th of second, so 6 = 100ms
|
||||
void BattinfoView::on_timer() {
|
||||
if (++timer_counter == timer_period) {
|
||||
timer_counter = 0;
|
||||
update_result();
|
||||
}
|
||||
}
|
||||
|
||||
void BattinfoView::update_result() {
|
||||
if (!battery::BatteryManagement::isDetected()) {
|
||||
// todo show no batt management
|
||||
text_percent.set("UNKNOWN");
|
||||
text_voltage.set("UNKNOWN");
|
||||
text_current.set("-");
|
||||
text_charge.set("-");
|
||||
return;
|
||||
}
|
||||
bool uichg = false;
|
||||
battery::BatteryManagement::getBatteryInfo(percent, voltage, current, isCharging);
|
||||
// update text fields
|
||||
if (percent <= 100)
|
||||
text_percent.set(to_string_dec_uint(percent) + " %");
|
||||
else
|
||||
text_percent.set("UNKNOWN");
|
||||
if (voltage > 1) {
|
||||
text_voltage.set(to_string_decimal(voltage / 1000.0, 3) + " V");
|
||||
} else {
|
||||
text_voltage.set("UNKNOWN");
|
||||
}
|
||||
if (current != 0) {
|
||||
if (labels_opt.hidden()) uichg = true;
|
||||
labels_opt.hidden(false);
|
||||
text_current.hidden(false);
|
||||
text_charge.hidden(false);
|
||||
text_current.set(to_string_dec_int(current) + " mA");
|
||||
text_charge.set(isCharging ? "charge" : "discharge");
|
||||
labels_opt.hidden(false);
|
||||
} else {
|
||||
if (!labels_opt.hidden()) uichg = true;
|
||||
labels_opt.hidden(true);
|
||||
text_current.hidden(true);
|
||||
text_charge.hidden(true);
|
||||
}
|
||||
if (uichg) set_dirty();
|
||||
// to update status bar too, send message in behalf of batt manager
|
||||
BatteryStateMessage msg{percent, isCharging, voltage};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
|
||||
BattinfoView::BattinfoView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&labels,
|
||||
&labels_opt,
|
||||
&text_percent,
|
||||
&text_voltage,
|
||||
&text_current,
|
||||
&text_charge,
|
||||
&button_exit});
|
||||
|
||||
button_exit.on_select = [this, &nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
update_result();
|
||||
needRun = true;
|
||||
thread = chThdCreateFromHeap(NULL, 512, NORMALPRIO + 10, BattinfoView::static_fn, this);
|
||||
}
|
||||
|
||||
msg_t BattinfoView::static_fn(void* arg) {
|
||||
auto obj = static_cast<BattinfoView*>(arg);
|
||||
while (needRun) {
|
||||
chThdSleepMilliseconds(16);
|
||||
obj->on_timer();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
BattinfoView::~BattinfoView() {
|
||||
needRun = false;
|
||||
if (thread) {
|
||||
chThdTerminate(thread);
|
||||
chThdWait(thread);
|
||||
thread = nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_BATTINFO_H__
|
||||
#define __UI_BATTINFO_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ui {
|
||||
class BattinfoView : public View {
|
||||
public:
|
||||
~BattinfoView();
|
||||
BattinfoView(NavigationView& nav);
|
||||
BattinfoView(const BattinfoView&) = delete;
|
||||
BattinfoView(BattinfoView&&) = delete;
|
||||
BattinfoView& operator=(const BattinfoView&) = delete;
|
||||
BattinfoView& operator=(BattinfoView&&) = delete;
|
||||
|
||||
void focus() override;
|
||||
std::string title() const override { return "Battery"; };
|
||||
|
||||
private:
|
||||
void update_result();
|
||||
void on_timer();
|
||||
NavigationView& nav_;
|
||||
uint16_t timer_period = 60;
|
||||
uint16_t timer_counter = 0;
|
||||
uint8_t percent = 0;
|
||||
uint16_t voltage = 0;
|
||||
int32_t current = 0;
|
||||
bool isCharging = false;
|
||||
|
||||
Labels labels{
|
||||
{{2 * 8, 1 * 16}, "Percent:", Color::light_grey()},
|
||||
{{2 * 8, 2 * 16}, "Voltage:", Color::light_grey()}};
|
||||
|
||||
Labels labels_opt{
|
||||
{{2 * 8, 3 * 16}, "Current:", Color::light_grey()},
|
||||
{{2 * 8, 4 * 16}, "Charge:", Color::light_grey()}};
|
||||
|
||||
Text text_percent{
|
||||
{13 * 8, 1 * 16, 10 * 16, 16},
|
||||
"-"};
|
||||
Text text_voltage{
|
||||
{13 * 8, 2 * 16, 10 * 16, 16},
|
||||
"-"};
|
||||
Text text_current{
|
||||
{13 * 8, 3 * 16, 10 * 16, 16},
|
||||
"-"};
|
||||
Text text_charge{
|
||||
{13 * 8, 4 * 16, 10 * 16, 16},
|
||||
"-"};
|
||||
|
||||
Button button_exit{
|
||||
{72, 17 * 16, 96, 32},
|
||||
"Back"};
|
||||
static msg_t static_fn(void* arg);
|
||||
static bool needRun;
|
||||
Thread* thread{nullptr};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_BATTINFO__*/
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_bmp_file_viewer.hpp"
|
||||
|
||||
extern ui::SystemView* system_view_ptr;
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
BMPFileViewer::BMPFileViewer(
|
||||
NavigationView& nav,
|
||||
const std::filesystem::path& path)
|
||||
: nav_{nav},
|
||||
path_{path} {
|
||||
add_children(
|
||||
{&bmp});
|
||||
bmp.set_enter_pass(true); // pass the enter key to me, so i can exit. this will disable zoom + pos reset
|
||||
set_focusable(true);
|
||||
system_view_ptr->set_app_fullscreen(true);
|
||||
}
|
||||
|
||||
BMPFileViewer::~BMPFileViewer() {
|
||||
system_view_ptr->set_app_fullscreen(false);
|
||||
}
|
||||
|
||||
void BMPFileViewer::focus() {
|
||||
bmp.focus();
|
||||
}
|
||||
|
||||
bool BMPFileViewer::on_key(KeyEvent k) {
|
||||
if (k == KeyEvent::Select) {
|
||||
nav_.pop();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BMPFileViewer::paint(Painter&) {
|
||||
bmp.load_bmp(path_);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_BMP_FILE_VIEWER_H__
|
||||
#define __UI_BMP_FILE_VIEWER_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "file.hpp"
|
||||
#include "ui_bmpview.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class BMPFileViewer : public View {
|
||||
public:
|
||||
BMPFileViewer(NavigationView& nav, const std::filesystem::path& path);
|
||||
~BMPFileViewer();
|
||||
bool on_key(KeyEvent key) override;
|
||||
void paint(Painter& painter) override;
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::filesystem::path path_{};
|
||||
BMPViewer bmp{{0, 0, 240, 320}};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif // __UI_BMP_FILE_VIEWER_H__
|
||||
@@ -41,6 +41,11 @@ DfuMenu::DfuMenu(NavigationView& nav)
|
||||
&text_info_line_8,
|
||||
&text_info_line_9,
|
||||
&text_info_line_10});
|
||||
|
||||
if (battery::BatteryManagement::isDetected()) {
|
||||
add_child(&voltage_label);
|
||||
add_child(&text_info_line_11);
|
||||
}
|
||||
}
|
||||
|
||||
void DfuMenu::paint(Painter& painter) {
|
||||
@@ -48,6 +53,8 @@ void DfuMenu::paint(Painter& painter) {
|
||||
size_t m0_fragmented_free_space = 0;
|
||||
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
|
||||
|
||||
auto lines = (battery::BatteryManagement::isDetected() ? 11 : 10) + 2;
|
||||
|
||||
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
||||
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
|
||||
text_info_line_3.set(to_string_dec_uint(m0_fragments, 6));
|
||||
@@ -58,9 +65,11 @@ void DfuMenu::paint(Painter& painter) {
|
||||
text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
||||
text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
||||
if (battery::BatteryManagement::isDetected()) {
|
||||
text_info_line_11.set(to_string_decimal_padding((float)battery::BatteryManagement::getVoltage() / 1000.0, 3, 6));
|
||||
}
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 10 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
|
||||
@@ -58,7 +58,9 @@ class DfuMenu : public View {
|
||||
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 stack:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "M4 cpu %:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
|
||||
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "Uptime:", Color::dark_cyan()}};
|
||||
|
||||
Labels voltage_label{{{6 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "Voltage:", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
@@ -70,6 +72,7 @@ class DfuMenu : public View {
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_9{{15 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_10{{15 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_11{{15 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
#include "ui_playlist.hpp"
|
||||
#include "ui_remote.hpp"
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_bmp_file_viewer.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_iq_trim.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -62,7 +64,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 +148,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 +236,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 +278,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 +330,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 +339,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 +397,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 +409,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 +451,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 +555,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 +579,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 +593,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 +605,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 +634,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 +643,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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -514,7 +653,10 @@ void FileManagerView::on_paste() {
|
||||
fs::filesystem_error result;
|
||||
|
||||
if (clipboard_mode == ClipboardMode::Cut)
|
||||
result = rename_file(clipboard_path, current_path / new_name);
|
||||
if ((current_path / clipboard_path.filename()) == clipboard_path)
|
||||
result = FR_OK; // Skip paste to avoid renaming if path is unchanged
|
||||
else
|
||||
result = rename_file(clipboard_path, current_path / new_name);
|
||||
|
||||
else if (clipboard_mode == ClipboardMode::Copy)
|
||||
result = copy_file(clipboard_path, current_path / new_name);
|
||||
@@ -525,14 +667,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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -554,8 +696,13 @@ bool FileManagerView::handle_file_open() {
|
||||
nav_.push<ScreenshotViewer>(path);
|
||||
return true;
|
||||
} else if (path_iequal(bmp_ext, ext)) {
|
||||
nav_.push<SplashViewer>(path);
|
||||
reload_current();
|
||||
if (path_iequal(current_path, u"/" + splash_dir)) {
|
||||
nav_.push<SplashViewer>(path); // splash, so load that viewer
|
||||
} else {
|
||||
nav_.push<BMPFileViewer>(path); // any other bmp
|
||||
}
|
||||
|
||||
reload_current(false);
|
||||
return true;
|
||||
} else if (path_iequal(rem_ext, ext)) {
|
||||
nav_.push<RemoteView>(path);
|
||||
@@ -567,7 +714,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(
|
||||
@@ -599,7 +746,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 {
|
||||
@@ -614,13 +761,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]() {
|
||||
@@ -639,7 +803,7 @@ FileManagerView::FileManagerView(
|
||||
};
|
||||
|
||||
button_cut.on_select = [this]() {
|
||||
if (selected_is_valid() && !get_selected_entry().is_directory) {
|
||||
if (selected_is_valid()) {
|
||||
clipboard_path = get_selected_full_path();
|
||||
clipboard_mode = ClipboardMode::Cut;
|
||||
} else
|
||||
@@ -658,7 +822,7 @@ FileManagerView::FileManagerView(
|
||||
if (clipboard_mode != ClipboardMode::None)
|
||||
on_paste();
|
||||
else
|
||||
nav_.display_modal("Paste", "Cut or copy a file first.");
|
||||
nav_.display_modal("Paste", " Cut or copy a file,\n or cut a folder, first.");
|
||||
};
|
||||
|
||||
button_new_dir.on_select = [this]() {
|
||||
@@ -678,7 +842,7 @@ FileManagerView::FileManagerView(
|
||||
};
|
||||
|
||||
button_rename_timestamp.on_select = [this]() {
|
||||
if (selected_is_valid() && !get_selected_entry().is_directory) {
|
||||
if (selected_is_valid()) {
|
||||
on_rename(::truncate(to_string_timestamp(rtc_time::now()), 8));
|
||||
} else
|
||||
nav_.display_modal("Timestamp Rename", "Can't rename that.");
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -163,8 +163,8 @@ bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
ui::Color::black());
|
||||
|
||||
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15 seconds.");
|
||||
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LEDs RX");
|
||||
painter.draw_string({12, 84}, this->nav_.style(), "and TX are flashing.");
|
||||
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LED RX");
|
||||
painter.draw_string({12, 84}, this->nav_.style(), "is on and TX is flashing.");
|
||||
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
|
||||
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
||||
|
||||
@@ -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"
|
||||
@@ -328,6 +329,10 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
if (audio::speaker_disable_supported()) {
|
||||
add_child(&toggle_speaker);
|
||||
}
|
||||
if (battery::BatteryManagement::isDetected()) {
|
||||
add_child(&toggle_battery_icon);
|
||||
add_child(&toggle_battery_text);
|
||||
}
|
||||
|
||||
checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen());
|
||||
checkbox_showsplash.set_value(pmem::config_splash());
|
||||
@@ -354,6 +359,8 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
toggle_speaker.set_value(!pmem::ui_hide_speaker());
|
||||
toggle_mute.set_value(!pmem::ui_hide_mute());
|
||||
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
|
||||
toggle_battery_icon.set_value(!pmem::ui_hide_battery_icon());
|
||||
toggle_battery_text.set_value(!pmem::ui_hide_numeric_battery());
|
||||
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
@@ -381,6 +388,8 @@ SetUIView::SetUIView(NavigationView& nav) {
|
||||
pmem::set_ui_hide_speaker(!toggle_speaker.value());
|
||||
pmem::set_ui_hide_mute(!toggle_mute.value());
|
||||
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
|
||||
pmem::set_ui_hide_battery_icon(!toggle_battery_icon.value());
|
||||
pmem::set_ui_hide_numeric_battery(!toggle_battery_text.value());
|
||||
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
|
||||
send_system_refresh();
|
||||
|
||||
@@ -635,13 +644,17 @@ void SetPersistentMemoryView::focus() {
|
||||
SetAudioView::SetAudioView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&field_tone_mix,
|
||||
&checkbox_beep_on_packets,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
field_tone_mix.set_value(pmem::tone_mix());
|
||||
|
||||
checkbox_beep_on_packets.set_value(pmem::beep_on_packets());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_tone_mix(field_tone_mix.value());
|
||||
pmem::set_beep_on_packets(checkbox_beep_on_packets.value());
|
||||
audio::output::update_audio_mute();
|
||||
nav.pop();
|
||||
};
|
||||
@@ -802,6 +815,7 @@ SetMenuColorView::SetMenuColorView(NavigationView& nav) {
|
||||
&field_green_level,
|
||||
&field_blue_level,
|
||||
&button_save,
|
||||
&button_reset,
|
||||
&button_cancel});
|
||||
|
||||
button_sample.set_focusable(false);
|
||||
@@ -819,6 +833,13 @@ SetMenuColorView::SetMenuColorView(NavigationView& nav) {
|
||||
field_green_level.on_change = color_changed_fn;
|
||||
field_blue_level.on_change = color_changed_fn;
|
||||
|
||||
button_reset.on_select = [&nav, this](Button&) {
|
||||
field_red_level.set_value(127);
|
||||
field_green_level.set_value(127);
|
||||
field_blue_level.set_value(127);
|
||||
set_dirty();
|
||||
};
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
|
||||
pmem::set_menu_color(c);
|
||||
@@ -835,6 +856,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)
|
||||
@@ -862,6 +937,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>(); }},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -323,37 +323,45 @@ class SetUIView : public View {
|
||||
};
|
||||
|
||||
ImageToggle toggle_camera{
|
||||
{6 * 8, 14 * 16 + 2, 16, 16},
|
||||
{2 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_camera};
|
||||
|
||||
ImageToggle toggle_sleep{
|
||||
{8 * 8, 14 * 16 + 2, 16, 16},
|
||||
{4 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_sleep};
|
||||
|
||||
ImageToggle toggle_stealth{
|
||||
{10 * 8, 14 * 16 + 2, 16, 16},
|
||||
{6 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_stealth};
|
||||
|
||||
ImageToggle toggle_converter{
|
||||
{12 * 8, 14 * 16 + 2, 16, 16},
|
||||
{8 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_upconvert};
|
||||
|
||||
ImageToggle toggle_bias_tee{
|
||||
{14 * 8, 14 * 16 + 2, 16, 16},
|
||||
{10 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_biast_off};
|
||||
|
||||
ImageToggle toggle_clock{
|
||||
{16 * 8, 14 * 16 + 2, 8, 16},
|
||||
{12 * 8, 14 * 16 + 2, 8, 16},
|
||||
&bitmap_icon_clk_ext};
|
||||
|
||||
ImageToggle toggle_mute{
|
||||
{17 * 8, 14 * 16 + 2, 16, 16},
|
||||
{13 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_and_headphones_mute};
|
||||
|
||||
ImageToggle toggle_speaker{
|
||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||
{15 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_speaker_mute};
|
||||
|
||||
ImageToggle toggle_battery_icon{
|
||||
{17 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_batt_icon};
|
||||
|
||||
ImageToggle toggle_battery_text{
|
||||
{19 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_batt_text};
|
||||
|
||||
ImageToggle toggle_fake_brightness{
|
||||
{21 * 8, 14 * 16 + 2, 16, 16},
|
||||
&bitmap_icon_brightness};
|
||||
@@ -506,8 +514,12 @@ class SetAudioView : public View {
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Controls the volume of the", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "tone when transmitting in", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "Soundboard or Mic apps.", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "Soundboard or Mic apps:", Color::light_grey()},
|
||||
{{2 * 8, 5 * 16}, "Tone key mix: %", Color::light_grey()},
|
||||
{{1 * 8, 8 * 16}, "Controls whether apps should", Color::light_grey()},
|
||||
{{1 * 8, 9 * 16}, "beep on speaker & headphone", Color::light_grey()},
|
||||
{{1 * 8, 10 * 16}, "when a packet is received", Color::light_grey()},
|
||||
{{1 * 8, 11 * 16}, "(not all apps support this):", Color::light_grey()},
|
||||
};
|
||||
|
||||
NumberField field_tone_mix{
|
||||
@@ -517,6 +529,11 @@ class SetAudioView : public View {
|
||||
1,
|
||||
'0'};
|
||||
|
||||
Checkbox checkbox_beep_on_packets{
|
||||
{3 * 8, 13 * 16},
|
||||
16,
|
||||
"Beep on RX packets"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
@@ -780,6 +797,11 @@ class SetMenuColorView : public View {
|
||||
' ',
|
||||
};
|
||||
|
||||
Button button_reset{
|
||||
{2 * 8, 13 * 16, 12 * 8, 32},
|
||||
"Reset",
|
||||
};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
@@ -790,6 +812,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);
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace portapack;
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "complex.hpp"
|
||||
@@ -54,7 +55,6 @@ SondeView::SondeView(NavigationView& nav)
|
||||
&field_vga,
|
||||
&rssi,
|
||||
&field_volume,
|
||||
&check_beep,
|
||||
&check_log,
|
||||
&check_crc,
|
||||
&text_signature,
|
||||
@@ -72,13 +72,6 @@ SondeView::SondeView(NavigationView& nav)
|
||||
|
||||
geopos.set_read_only(true);
|
||||
|
||||
check_beep.set_value(beep);
|
||||
check_beep.on_select = [this](Checkbox&, bool v) {
|
||||
beep = v;
|
||||
if (beep)
|
||||
baseband::request_audio_beep(1000, 24000, 60); // 1khz tone for 60ms to acknowledge enablement
|
||||
};
|
||||
|
||||
check_log.set_value(logging);
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
logging = v;
|
||||
@@ -115,7 +108,10 @@ SondeView::SondeView(NavigationView& nav)
|
||||
if (logger)
|
||||
logger->append(logs_dir / u"SONDE.TXT");
|
||||
|
||||
audio::output::start();
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
// inject a PitchRSSIConfigureMessage in order to arm
|
||||
// the pitch rssi events that will be used by the
|
||||
@@ -185,7 +181,7 @@ void SondeView::on_packet(const sonde::Packet& packet) {
|
||||
logger->on_packet(packet);
|
||||
}
|
||||
|
||||
if (beep) {
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_rssi_beep();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,14 +75,12 @@ class SondeView : public View {
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */
|
||||
};
|
||||
bool beep{false};
|
||||
bool logging{false};
|
||||
bool use_crc{false};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_sonde",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"beep"sv, &beep},
|
||||
{"logging"sv, &logging},
|
||||
{"use_crc"sv, &use_crc},
|
||||
}};
|
||||
@@ -124,11 +122,6 @@ class SondeView : public View {
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
Checkbox check_beep{
|
||||
{22 * 8, 6 * 16},
|
||||
3,
|
||||
"Beep"};
|
||||
|
||||
Checkbox check_log{
|
||||
{22 * 8, 8 * 16},
|
||||
3,
|
||||
|
||||
@@ -156,7 +156,6 @@ void SSTVTXView::start_tx() {
|
||||
// The baseband SSTV TX code (proc_sstv) has a 2-scanline buffer. It is preloaded before
|
||||
// TX start, and asks for fill-up when a new scanline starts being read. This should
|
||||
// leave enough time for the code in prepare_scanline() before it ends.
|
||||
|
||||
scanline_counter = 0;
|
||||
prepare_scanline(); // Preload one scanline
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_standalone_view.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) {
|
||||
// TODO: collect memory on terminate, once this is used
|
||||
chThdCreateFromHeap(NULL, stack_size, priority, fn, arg);
|
||||
}
|
||||
|
||||
void fill_rectangle(int x, int y, int width, int height, uint16_t color) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({x, y, width, height}, ui::Color(color));
|
||||
}
|
||||
|
||||
void* alloc(size_t size) {
|
||||
void* p = chHeapAlloc(0x0, size);
|
||||
if (p == nullptr)
|
||||
chDbgPanic("Out of Memory");
|
||||
return p;
|
||||
}
|
||||
|
||||
uint64_t get_switches_state_ulong() {
|
||||
return get_switches_state().to_ulong();
|
||||
}
|
||||
|
||||
standalone_application_api_t api = {
|
||||
/* .malloc = */ &alloc,
|
||||
/* .calloc = */ &calloc,
|
||||
/* .realloc = */ &realloc,
|
||||
/* .free = */ &chHeapFree,
|
||||
/* .create_thread = */ &create_thread,
|
||||
/* .fill_rectangle = */ &fill_rectangle,
|
||||
/* .swizzled_switches = */ &swizzled_switches,
|
||||
/* .get_switches_state = */ &get_switches_state_ulong,
|
||||
};
|
||||
|
||||
StandaloneView::StandaloneView(NavigationView& nav, std::unique_ptr<uint8_t[]> app_image)
|
||||
: nav_(nav), _app_image(std::move(app_image)) {
|
||||
get_application_information()->initialize(api);
|
||||
add_children({&dummy});
|
||||
}
|
||||
|
||||
void StandaloneView::focus() {
|
||||
dummy.focus();
|
||||
}
|
||||
|
||||
void StandaloneView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
}
|
||||
|
||||
void StandaloneView::frame_sync() {
|
||||
// skip first refresh
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
} else {
|
||||
get_application_information()->on_event(1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
+15
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
* Copyright (C) 2024 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -19,22 +19,24 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_PACMAN_H__
|
||||
#define __UI_PACMAN_H__
|
||||
#ifndef __UI_STANDALONE_VIEW_H__
|
||||
#define __UI_STANDALONE_VIEW_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "standalone_app.hpp"
|
||||
|
||||
namespace ui::external_app::pacman {
|
||||
namespace ui {
|
||||
|
||||
class PacmanView : public View {
|
||||
class StandaloneView : public View {
|
||||
public:
|
||||
PacmanView(NavigationView& nav);
|
||||
StandaloneView(NavigationView& nav, std::unique_ptr<uint8_t[]> app_image);
|
||||
virtual ~StandaloneView() override { get_application_information()->shutdown(); }
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Pac-Man"; };
|
||||
std::string title() const override { return (const char*)get_application_information()->app_name; };
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
@@ -42,6 +44,10 @@ class PacmanView : public View {
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
std::unique_ptr<uint8_t[]> _app_image;
|
||||
standalone_application_information_t* get_application_information() const {
|
||||
return reinterpret_cast<standalone_application_information_t*>(_app_image.get());
|
||||
}
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
@@ -54,6 +60,6 @@ class PacmanView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::pacman
|
||||
} // namespace ui
|
||||
|
||||
#endif /*__UI_PACMAN_H__*/
|
||||
#endif /*__UI_STANDALONE_VIEW_H__*/
|
||||
@@ -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);
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
extern ui::SystemView* system_view_ptr;
|
||||
|
||||
namespace ui {
|
||||
|
||||
TouchCalibrationView::TouchCalibrationView(
|
||||
@@ -40,6 +42,7 @@ TouchCalibrationView::TouchCalibrationView(
|
||||
&image_verify_1,
|
||||
&image_verify_2,
|
||||
&label_calibrate,
|
||||
&label_calibrate_2,
|
||||
&label_verify,
|
||||
&label_success,
|
||||
&label_failure,
|
||||
@@ -51,6 +54,12 @@ TouchCalibrationView::TouchCalibrationView(
|
||||
button_ok.on_select = [this](Button&) { this->on_ok(); };
|
||||
|
||||
set_phase(Phase::Calibrate0);
|
||||
|
||||
system_view_ptr->get_status_view()->set_back_hidden(true);
|
||||
}
|
||||
|
||||
TouchCalibrationView::~TouchCalibrationView() {
|
||||
system_view_ptr->get_status_view()->set_back_hidden(false);
|
||||
}
|
||||
|
||||
void TouchCalibrationView::focus() {
|
||||
@@ -70,6 +79,7 @@ void TouchCalibrationView::update_target() {
|
||||
image_verify_2.hidden(phase != Phase::Verify2);
|
||||
|
||||
label_calibrate.hidden(!phase_calibrate);
|
||||
label_calibrate_2.hidden(!phase_calibrate && !phase_verify);
|
||||
label_verify.hidden(!phase_verify);
|
||||
label_success.hidden(phase != Phase::Success);
|
||||
label_failure.hidden(phase != Phase::Failure);
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace ui {
|
||||
class TouchCalibrationView : public View {
|
||||
public:
|
||||
TouchCalibrationView(NavigationView& nav);
|
||||
~TouchCalibrationView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
@@ -110,9 +111,13 @@ class TouchCalibrationView : public View {
|
||||
Color::black()};
|
||||
|
||||
Text label_calibrate{
|
||||
{16, 5 * 16, 26 * 8, 1 * 16},
|
||||
{2 * 8, 5 * 16, 26 * 8, 1 * 16},
|
||||
"Touch targets to calibrate"};
|
||||
|
||||
Text label_calibrate_2{
|
||||
{1 * 8, 6 * 16, 28 * 8, 1 * 16},
|
||||
"(hold position using stylus)"};
|
||||
|
||||
Text label_verify{
|
||||
{28, 5 * 16, 23 * 8, 1 * 16},
|
||||
"Touch targets to verify"};
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void WeatherRecentEntryDetailView::update_data() {
|
||||
@@ -91,6 +93,7 @@ WeatherView::WeatherView(NavigationView& nav)
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_volume,
|
||||
&field_frequency,
|
||||
&options_temperature,
|
||||
&button_clear_list,
|
||||
@@ -102,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;
|
||||
@@ -120,6 +123,11 @@ WeatherView::WeatherView(NavigationView& nav)
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
on_tick_second();
|
||||
};
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
}
|
||||
|
||||
void WeatherView::on_tick_second() {
|
||||
@@ -142,10 +150,15 @@ void WeatherView::on_data(const WeatherDataMessage* data) {
|
||||
truncate_entries(recent, 64);
|
||||
}
|
||||
recent_entries_view.set_dirty();
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
}
|
||||
|
||||
WeatherView::~WeatherView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
@@ -126,6 +126,10 @@ class WeatherView : public View {
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
@@ -2447,6 +2447,82 @@ static constexpr Bitmap bitmap_icon_camera{
|
||||
{16, 16},
|
||||
bitmap_icon_camera_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_batt_icon_data[] = {
|
||||
0xC0,
|
||||
0x03,
|
||||
0xC0,
|
||||
0x03,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0x10,
|
||||
0x08,
|
||||
0x10,
|
||||
0x08,
|
||||
0x10,
|
||||
0x08,
|
||||
0x10,
|
||||
0x08,
|
||||
0x10,
|
||||
0x08,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_batt_icon{
|
||||
{16, 16},
|
||||
bitmap_icon_batt_icon_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_batt_text_data[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x30,
|
||||
0x06,
|
||||
0x48,
|
||||
0x09,
|
||||
0x48,
|
||||
0x09,
|
||||
0x70,
|
||||
0x0E,
|
||||
0x40,
|
||||
0x08,
|
||||
0x30,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x48,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x48,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
static constexpr Bitmap bitmap_icon_batt_text{
|
||||
{16, 16},
|
||||
bitmap_icon_batt_text_data};
|
||||
|
||||
static constexpr uint8_t bitmap_icon_tools_wipesd_data[] = {
|
||||
0xF0,
|
||||
0x3F,
|
||||
|
||||
+10
-5
@@ -1,9 +1,5 @@
|
||||
set(EXTCPPSRC
|
||||
|
||||
#pacman
|
||||
external/pacman/main.cpp
|
||||
external/pacman/ui_pacman.cpp
|
||||
|
||||
#tetris
|
||||
external/tetris/main.cpp
|
||||
external/tetris/ui_tetris.cpp
|
||||
@@ -76,10 +72,17 @@ set(EXTCPPSRC
|
||||
#audio_test
|
||||
external/audio_test/main.cpp
|
||||
external/audio_test/ui_audio_test.cpp
|
||||
|
||||
#wardrivemap
|
||||
external/wardrivemap/main.cpp
|
||||
external/wardrivemap/ui_wardrivemap.cpp
|
||||
|
||||
#tpmsrx
|
||||
external/tpmsrx/main.cpp
|
||||
external/tpmsrx/tpms_app.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
pacman
|
||||
afsk_rx
|
||||
calculator
|
||||
font_viewer
|
||||
@@ -97,4 +100,6 @@ set(EXTAPPLIST
|
||||
extsensors
|
||||
foxhunt_rx
|
||||
audio_test
|
||||
wardrivemap
|
||||
tpmsrx
|
||||
)
|
||||
|
||||
+14
-7
@@ -23,7 +23,6 @@ MEMORY
|
||||
* Also need to consider processor memory map - reading 0xADxxxxxx generates a fault which may be better than unexpected behavior.
|
||||
* External app address ranges below must match those in python file "external_app_info.py".
|
||||
*/
|
||||
ram_external_app_pacman (rwx) : org = 0xADB00000, len = 32k
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xADB10000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xADB20000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xADB30000, len = 32k
|
||||
@@ -41,16 +40,12 @@ MEMORY
|
||||
ram_external_app_extsensors(rwx) : org = 0xADBF0000, len = 32k
|
||||
ram_external_app_foxhunt_rx(rwx) : org = 0xADC00000, len = 32k
|
||||
ram_external_app_audio_test(rwx) : org = 0xADC10000, len = 32k
|
||||
ram_external_app_wardrivemap(rwx) : org = 0xADC20000, len = 32k
|
||||
ram_external_app_tpmsrx(rwx) : org = 0xADC30000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.external_app_pacman : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_pacman.application_information));
|
||||
*(*ui*external_app*pacman*);
|
||||
} > ram_external_app_pacman
|
||||
|
||||
.external_app_afsk_rx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_afsk_rx.application_information));
|
||||
@@ -153,4 +148,16 @@ SECTIONS
|
||||
*(*ui*external_app*audio_test*);
|
||||
} > ram_external_app_audio_test
|
||||
|
||||
.external_app_wardrivemap : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_wardrivemap.application_information));
|
||||
*(*ui*external_app*wardrivemap*);
|
||||
} > ram_external_app_wardrivemap
|
||||
|
||||
.external_app_tpmsrx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_tpmsrx.application_information));
|
||||
*(*ui*external_app*tpmsrx*);
|
||||
} > ram_external_app_tpmsrx
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -62,8 +62,7 @@ FoxhuntRxView::FoxhuntRxView(NavigationView& nav)
|
||||
};
|
||||
geomap.set_mode(DISPLAY);
|
||||
geomap.set_manual_panning(false);
|
||||
// geomap.set_enable_additional_zoom(true);
|
||||
// geomap.set_hide_center_marker(true); //todo hide again after testing
|
||||
// geomap.set_hide_center_marker(true); //todo test if needed
|
||||
geomap.set_focusable(true);
|
||||
geomap.clear_markers();
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#include "ui_pacman.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
namespace ui::external_app::pacman {
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
// external code, so ignore warnings
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#pragma GCC diagnostic ignored "-Wreturn-type"
|
||||
#pragma GCC diagnostic ignored "-Weffc++"
|
||||
#include "playfield.hpp"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
PacmanView::PacmanView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&dummy});
|
||||
}
|
||||
|
||||
void PacmanView::focus() {
|
||||
dummy.focus();
|
||||
}
|
||||
|
||||
void PacmanView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
static Playfield _game;
|
||||
static bool wait_for_button_release{false};
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
_game.Init();
|
||||
}
|
||||
|
||||
auto switches_raw = swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu));
|
||||
auto switches_debounced = get_switches_state().to_ulong();
|
||||
|
||||
// For the Select (Start/Pause) button, wait for release to avoid repeat
|
||||
uint8_t buttons_to_wait_for = (1 << (int)Switch::Sel);
|
||||
if (wait_for_button_release) {
|
||||
if ((switches_debounced & buttons_to_wait_for) == 0)
|
||||
wait_for_button_release = false;
|
||||
switches_debounced &= ~buttons_to_wait_for;
|
||||
} else {
|
||||
if (switches_debounced & buttons_to_wait_for)
|
||||
wait_for_button_release = true;
|
||||
}
|
||||
|
||||
// For the directional buttons, use the raw inputs for fastest response time
|
||||
but_RIGHT = (switches_raw & (1 << (int)Switch::Right)) != 0;
|
||||
but_LEFT = (switches_raw & (1 << (int)Switch::Left)) != 0;
|
||||
but_DOWN = (switches_raw & (1 << (int)Switch::Down)) != 0;
|
||||
but_UP = (switches_raw & (1 << (int)Switch::Up)) != 0;
|
||||
|
||||
// For the pause button, use the debounced input to avoid glitches, and OR in the value to make sure that we don't clear it before it's seen
|
||||
but_A |= (switches_debounced & (1 << (int)Switch::Sel)) != 0;
|
||||
|
||||
_game.Step();
|
||||
}
|
||||
|
||||
void PacmanView::frame_sync() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::pacman
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "tpms_app.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::tpmsrx {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<TPMSAppView>();
|
||||
}
|
||||
} // namespace ui::external_app::tpmsrx
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_tpmsrx.application_information"), used)) application_information_t _application_information_tpmsrx = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time
|
||||
/*.externalAppEntry = */ ui::external_app::tpmsrx::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "TPMS RX",
|
||||
/*.bitmap_data = */ {
|
||||
0xC0,
|
||||
0x03,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0x18,
|
||||
0x18,
|
||||
0xEC,
|
||||
0x37,
|
||||
0x36,
|
||||
0x6D,
|
||||
0x3A,
|
||||
0x59,
|
||||
0x4B,
|
||||
0xD5,
|
||||
0x8B,
|
||||
0xD3,
|
||||
0xCB,
|
||||
0xD1,
|
||||
0xAB,
|
||||
0xD2,
|
||||
0x9A,
|
||||
0x5C,
|
||||
0xB6,
|
||||
0x6C,
|
||||
0xEC,
|
||||
0x37,
|
||||
0x18,
|
||||
0x18,
|
||||
0xF0,
|
||||
0x0F,
|
||||
0xC0,
|
||||
0x03,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_tpms */ {'P', 'T', 'P', 'M'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+76
-62
@@ -23,7 +23,7 @@
|
||||
#include "tpms_app.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "audio.hpp"
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
@@ -32,15 +32,17 @@ using namespace portapack;
|
||||
#include "utility.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
namespace tpms {
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui::external_app::tpmsrx {
|
||||
|
||||
namespace format {
|
||||
|
||||
std::string type(Reading::Type type) {
|
||||
std::string type(tpms::Reading::Type type) {
|
||||
return to_string_dec_uint(toUType(type), 2);
|
||||
}
|
||||
|
||||
std::string id(TransponderID id) {
|
||||
std::string id(tpms::TransponderID id) {
|
||||
return to_string_hex(id.value(), 8);
|
||||
}
|
||||
|
||||
@@ -52,17 +54,17 @@ std::string temperature(Temperature temperature) {
|
||||
return to_string_dec_int(units_fahr ? temperature.fahrenheit() : temperature.celsius(), 3);
|
||||
}
|
||||
|
||||
std::string flags(Flags flags) {
|
||||
std::string flags(tpms::Flags flags) {
|
||||
return to_string_hex(flags, 2);
|
||||
}
|
||||
|
||||
static std::string signal_type(SignalType signal_type) {
|
||||
static std::string signal_type(tpms::SignalType signal_type) {
|
||||
switch (signal_type) {
|
||||
case SignalType::FSK_19k2_Schrader:
|
||||
case tpms::SignalType::FSK_19k2_Schrader:
|
||||
return "FSK 38400 19200 Schrader";
|
||||
case SignalType::OOK_8k192_Schrader:
|
||||
case tpms::SignalType::OOK_8k192_Schrader:
|
||||
return "OOK - 8192 Schrader";
|
||||
case SignalType::OOK_8k4_Schrader:
|
||||
case tpms::SignalType::OOK_8k4_Schrader:
|
||||
return "OOK - 8400 Schrader";
|
||||
default:
|
||||
return "- - - -";
|
||||
@@ -71,15 +73,13 @@ static std::string signal_type(SignalType signal_type) {
|
||||
|
||||
} /* namespace format */
|
||||
|
||||
} /* namespace tpms */
|
||||
|
||||
void TPMSLogger::on_packet(const tpms::Packet& packet, const uint32_t target_frequency) {
|
||||
const auto hex_formatted = packet.symbols_formatted();
|
||||
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
|
||||
|
||||
std::string entry = target_frequency_str + " " + tpms::format::signal_type(packet.signal_type()) + " " + hex_formatted.data + "/" + hex_formatted.errors;
|
||||
std::string entry = target_frequency_str + " " + ui::external_app::tpmsrx::format::signal_type(packet.signal_type()) + " " + hex_formatted.data + "/" + hex_formatted.errors;
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
}
|
||||
|
||||
@@ -99,54 +99,12 @@ void TPMSRecentEntry::update(const tpms::Reading& reading) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <>
|
||||
void RecentEntriesTable<TPMSRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
std::string line = tpms::format::type(entry.type) + " " + tpms::format::id(entry.id);
|
||||
|
||||
if (entry.last_pressure.is_valid()) {
|
||||
line += " " + tpms::format::pressure(entry.last_pressure.value());
|
||||
} else {
|
||||
line +=
|
||||
" "
|
||||
" ";
|
||||
}
|
||||
|
||||
if (entry.last_temperature.is_valid()) {
|
||||
line += " " + tpms::format::temperature(entry.last_temperature.value());
|
||||
} else {
|
||||
line +=
|
||||
" "
|
||||
" ";
|
||||
}
|
||||
|
||||
if (entry.received_count > 999) {
|
||||
line += " +++";
|
||||
} else {
|
||||
line += " " + to_string_dec_uint(entry.received_count, 3);
|
||||
}
|
||||
|
||||
if (entry.last_flags.is_valid()) {
|
||||
line += " " + tpms::format::flags(entry.last_flags.value());
|
||||
} else {
|
||||
line +=
|
||||
" "
|
||||
" ";
|
||||
}
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
}
|
||||
|
||||
TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_tpms);
|
||||
// baseband::run_image(portapack::spi_flash::image_tag_tpms);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({&rssi,
|
||||
&field_volume,
|
||||
&channel,
|
||||
&options_band,
|
||||
&options_pressure,
|
||||
@@ -164,24 +122,30 @@ TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
options_band.set_by_value(receiver_model.target_frequency());
|
||||
|
||||
options_pressure.on_change = [this](size_t, int32_t i) {
|
||||
tpms::format::units_psi = (bool)i;
|
||||
format::units_psi = (bool)i;
|
||||
update_view();
|
||||
};
|
||||
options_pressure.set_selected_index(tpms::format::units_psi, true);
|
||||
options_pressure.set_selected_index(format::units_psi, true);
|
||||
|
||||
options_temperature.on_change = [this](size_t, int32_t i) {
|
||||
tpms::format::units_fahr = (bool)i;
|
||||
format::units_fahr = (bool)i;
|
||||
update_view();
|
||||
};
|
||||
options_temperature.set_selected_index(tpms::format::units_fahr, true);
|
||||
options_temperature.set_selected_index(format::units_fahr, true);
|
||||
|
||||
logger = std::make_unique<TPMSLogger>();
|
||||
if (logger) {
|
||||
logger->append(logs_dir / u"TPMS.TXT");
|
||||
}
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
}
|
||||
|
||||
TPMSAppView::~TPMSAppView() {
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -214,6 +178,10 @@ void TPMSAppView::on_packet(const tpms::Packet& packet) {
|
||||
entry.update(reading);
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
|
||||
if (pmem::beep_on_packets()) {
|
||||
baseband::request_audio_beep(1000, 24000, 60);
|
||||
}
|
||||
}
|
||||
|
||||
void TPMSAppView::on_show_list() {
|
||||
@@ -221,4 +189,50 @@ void TPMSAppView::on_show_list() {
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::tpmsrx
|
||||
|
||||
namespace ui {
|
||||
|
||||
template <>
|
||||
void RecentEntriesTable<ui::external_app::tpmsrx::TPMSRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
std::string line = ui::external_app::tpmsrx::format::type(entry.type) + " " + ui::external_app::tpmsrx::format::id(entry.id);
|
||||
|
||||
if (entry.last_pressure.is_valid()) {
|
||||
line += " " + ui::external_app::tpmsrx::format::pressure(entry.last_pressure.value());
|
||||
} else {
|
||||
line +=
|
||||
" "
|
||||
" ";
|
||||
}
|
||||
|
||||
if (entry.last_temperature.is_valid()) {
|
||||
line += " " + ui::external_app::tpmsrx::format::temperature(entry.last_temperature.value());
|
||||
} else {
|
||||
line +=
|
||||
" "
|
||||
" ";
|
||||
}
|
||||
|
||||
if (entry.received_count > 999) {
|
||||
line += " +++";
|
||||
} else {
|
||||
line += " " + to_string_dec_uint(entry.received_count, 3);
|
||||
}
|
||||
|
||||
if (entry.last_flags.is_valid()) {
|
||||
line += " " + ui::external_app::tpmsrx::format::flags(entry.last_flags.value());
|
||||
} else {
|
||||
line +=
|
||||
" "
|
||||
" ";
|
||||
}
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
+11
-14
@@ -38,7 +38,7 @@
|
||||
|
||||
#include "tpms_packet.hpp"
|
||||
|
||||
namespace tpms {
|
||||
namespace ui::external_app::tpmsrx {
|
||||
|
||||
namespace format {
|
||||
|
||||
@@ -47,12 +47,6 @@ static bool units_fahr{false};
|
||||
|
||||
} /* namespace format */
|
||||
|
||||
} /* namespace tpms */
|
||||
|
||||
namespace std {
|
||||
|
||||
} /* namespace std */
|
||||
|
||||
struct TPMSRecentEntry {
|
||||
using Key = std::pair<tpms::Reading::Type, tpms::TransponderID>;
|
||||
|
||||
@@ -94,8 +88,6 @@ class TPMSLogger {
|
||||
LogFile log_file{};
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
using TPMSRecentEntriesView = RecentEntriesView<TPMSRecentEntries>;
|
||||
|
||||
class TPMSAppView : public View {
|
||||
@@ -115,16 +107,18 @@ class TPMSAppView : public View {
|
||||
|
||||
private:
|
||||
RxRadioState radio_state_{
|
||||
314950000 /* frequency*/,
|
||||
314950000 /* frequency*/
|
||||
,
|
||||
1750000 /* bandwidth */,
|
||||
2457600 /* sampling rate */};
|
||||
2457600 /* sampling rate */
|
||||
};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_tpms",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"units_psi"sv, &tpms::format::units_psi},
|
||||
{"units_fahr"sv, &tpms::format::units_fahr},
|
||||
{"units_psi"sv, &format::units_psi},
|
||||
{"units_fahr"sv, &format::units_fahr},
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
@@ -143,6 +137,9 @@ class TPMSAppView : public View {
|
||||
{21 * 8, 0, 6 * 8, 4},
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
@@ -195,6 +192,6 @@ class TPMSAppView : public View {
|
||||
void update_view();
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::tpmsrx
|
||||
|
||||
#endif /*__TPMS_APP_H__*/
|
||||
Vendored
+33
-33
@@ -20,58 +20,58 @@
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_pacman.hpp"
|
||||
#include "ui_wardrivemap.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::pacman {
|
||||
namespace ui::external_app::wardrivemap {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<PacmanView>();
|
||||
nav.push<WardriveMapView>();
|
||||
}
|
||||
} // namespace ui::external_app::pacman
|
||||
} // namespace ui::external_app::wardrivemap
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_pacman.application_information"), used)) application_information_t _application_information_pacman = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time
|
||||
/*.externalAppEntry = */ ui::external_app::pacman::initialize_app,
|
||||
__attribute__((section(".external_app.app_wardrivemap.application_information"), used)) application_information_t _application_information_wardrivemap = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::wardrivemap::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Pac-Man",
|
||||
/*.app_name = */ "WardriveMap",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x20,
|
||||
0x12,
|
||||
0x48,
|
||||
0x8A,
|
||||
0x51,
|
||||
0xCA,
|
||||
0x53,
|
||||
0xCA,
|
||||
0x53,
|
||||
0x8A,
|
||||
0x51,
|
||||
0x12,
|
||||
0x48,
|
||||
0x84,
|
||||
0x21,
|
||||
0xC0,
|
||||
0x07,
|
||||
0xE0,
|
||||
0x0F,
|
||||
0x03,
|
||||
0x40,
|
||||
0x02,
|
||||
0x60,
|
||||
0x06,
|
||||
0x20,
|
||||
0x04,
|
||||
0x30,
|
||||
0x0C,
|
||||
0xF0,
|
||||
0x1F,
|
||||
0xF8,
|
||||
0x07,
|
||||
0xF8,
|
||||
0x01,
|
||||
0x78,
|
||||
0x00,
|
||||
0xF8,
|
||||
0x01,
|
||||
0xF8,
|
||||
0x07,
|
||||
0xF0,
|
||||
0x1F,
|
||||
0xE0,
|
||||
0x0F,
|
||||
0xC0,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_wardrivemap.hpp"
|
||||
|
||||
#include "rtc_time.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "metadata_file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::wardrivemap {
|
||||
|
||||
void WardriveMapView::focus() {
|
||||
geopos.focus();
|
||||
}
|
||||
|
||||
// needs to load on every map change, because won't store or draw all while marker is not in the current view.
|
||||
// todo optimize this somehow
|
||||
bool WardriveMapView::load_markers() {
|
||||
uint16_t cnt = 0;
|
||||
uint16_t displayed_cnt = 0;
|
||||
geomap.clear_markers();
|
||||
// serach for files with geotag, and add it to geomap as marker with tag. limit to N bc of mem limit.
|
||||
for (const auto& entry : std::filesystem::directory_iterator(captures_dir, u"*.txt")) {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
if (displayed_cnt > 30) break;
|
||||
std::filesystem::path pth = captures_dir;
|
||||
pth += u"/" + entry.path();
|
||||
auto metadata_path = get_metadata_path(pth);
|
||||
auto metadata = read_metadata_file(metadata_path);
|
||||
|
||||
if (metadata) {
|
||||
if (metadata.value().latitude != 0 && metadata.value().longitude != 0 && metadata.value().latitude < 400 && metadata.value().longitude < 400) {
|
||||
if (first_init == false) {
|
||||
// move map there before add, so will display this.
|
||||
geopos.set_report_change(false);
|
||||
geopos.set_lat(metadata.value().latitude);
|
||||
geopos.set_lon(metadata.value().longitude);
|
||||
geopos.set_report_change(true);
|
||||
geomap.move(metadata.value().longitude, metadata.value().latitude);
|
||||
first_init = true;
|
||||
}
|
||||
GeoMarker tmp{metadata.value().latitude, metadata.value().longitude, 400, entry.path().filename().string()};
|
||||
if (geomap.store_marker(tmp) == MapMarkerStored::MARKER_STORED) displayed_cnt++;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (cnt > 0);
|
||||
}
|
||||
|
||||
WardriveMapView::WardriveMapView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&text_info,
|
||||
&geomap,
|
||||
&geopos,
|
||||
&text_notfound});
|
||||
|
||||
geomap.set_mode(DISPLAY);
|
||||
geomap.set_manual_panning(false);
|
||||
geomap.set_focusable(true);
|
||||
geomap.set_hide_center_marker(true);
|
||||
geomap.clear_markers();
|
||||
|
||||
geopos.set_report_change(false);
|
||||
geopos.set_lat(0);
|
||||
geopos.set_lon(0);
|
||||
geopos.set_altitude(0);
|
||||
geopos.set_speed(0);
|
||||
geopos.set_read_only(true);
|
||||
geopos.hide_altandspeed();
|
||||
geopos.set_report_change(true);
|
||||
|
||||
geopos.on_change = [this](int32_t altitude, float lat, float lon, int32_t speed) {
|
||||
(void)altitude;
|
||||
(void)speed;
|
||||
geomap.set_manual_panning(true);
|
||||
geomap.move(lon, lat);
|
||||
load_markers();
|
||||
geomap.set_dirty();
|
||||
};
|
||||
text_notfound.hidden(true);
|
||||
geomap.init();
|
||||
// load markers
|
||||
if (load_markers()) {
|
||||
text_notfound.hidden(true);
|
||||
geomap.set_dirty();
|
||||
} else {
|
||||
geomap.hidden(true);
|
||||
geopos.hidden(true);
|
||||
}
|
||||
geomap.on_move = [this](float lon, float lat) {
|
||||
(void)lon;
|
||||
(void)lat;
|
||||
load_markers();
|
||||
};
|
||||
}
|
||||
|
||||
WardriveMapView::~WardriveMapView() {
|
||||
}
|
||||
|
||||
void WardriveMapView::on_gps(const GPSPosDataMessage* msg) {
|
||||
geomap.update_my_position(msg->lat, msg->lon, msg->altitude);
|
||||
if (geomap.manual_panning() == false) {
|
||||
geopos.set_report_change(false);
|
||||
geopos.set_lat(msg->lat);
|
||||
geopos.set_lon(msg->lon);
|
||||
geopos.set_altitude(msg->altitude);
|
||||
geopos.set_speed(msg->speed);
|
||||
geopos.set_report_change(true);
|
||||
geomap.move(msg->lon, msg->lat);
|
||||
load_markers();
|
||||
}
|
||||
geomap.set_dirty();
|
||||
}
|
||||
|
||||
void WardriveMapView::on_orientation(const OrientationDataMessage* msg) {
|
||||
geomap.set_angle(msg->angle);
|
||||
geomap.update_my_orientation(msg->angle, true);
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::wardrivemap
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// Code from https://github.com/Flipper-XFW/Xtreme-Apps/tree/04c3a60093e2c2378e79498b4505aa8072980a42/ble_spam/protocols
|
||||
// Thanks for the work of the original creators!
|
||||
// Saying thanks in the main view!
|
||||
|
||||
#ifndef __UI_WARDRIVEMAP_H__
|
||||
#define __UI_WARDRIVEMAP_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_geomap.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::wardrivemap {
|
||||
|
||||
class WardriveMapView : public View {
|
||||
public:
|
||||
WardriveMapView(NavigationView& nav);
|
||||
~WardriveMapView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override {
|
||||
return "WardriveMap";
|
||||
};
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Text text_info{{0 * 8, 0 * 8, 30 * 8, 16 * 1}, "All GEOTAG from CAPTURES"};
|
||||
Text text_notfound{{0 * 8, 3 * 8, 30 * 8, 16 * 1}, "No GeoTagged captures found"};
|
||||
GeoPos geopos{
|
||||
{0, 20},
|
||||
GeoPos::alt_unit::METERS,
|
||||
GeoPos::spd_unit::HIDDEN};
|
||||
GeoMap geomap{{0, 75, 240, 320 - 75}};
|
||||
|
||||
void on_gps(const GPSPosDataMessage* msg);
|
||||
void on_orientation(const OrientationDataMessage* msg);
|
||||
|
||||
bool load_markers(); // returns true if any exists, false if none.
|
||||
|
||||
bool first_init = false;
|
||||
|
||||
MessageHandlerRegistration message_handler_gps{
|
||||
Message::ID::GPSPosData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const GPSPosDataMessage*>(p);
|
||||
this->on_gps(message);
|
||||
}};
|
||||
MessageHandlerRegistration message_handler_orientation{
|
||||
Message::ID::OrientationData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const OrientationDataMessage*>(p);
|
||||
this->on_orientation(message);
|
||||
}};
|
||||
};
|
||||
}; // namespace ui::external_app::wardrivemap
|
||||
|
||||
#endif /*__UI_WARDRIVEMAP_H__*/
|
||||
@@ -70,6 +70,10 @@ File::~File() {
|
||||
f_close(&f);
|
||||
}
|
||||
|
||||
void File::close() {
|
||||
f_close(&f);
|
||||
}
|
||||
|
||||
File::Result<File::Size> File::read(void* data, Size bytes_to_read) {
|
||||
UINT bytes_read = 0;
|
||||
const auto result = f_read(&f, data, bytes_to_read, &bytes_read);
|
||||
@@ -98,6 +102,10 @@ File::Offset File::tell() const {
|
||||
return f_tell(&f);
|
||||
}
|
||||
|
||||
File::Result<bool> File::eof() {
|
||||
return f_eof(&f);
|
||||
}
|
||||
|
||||
File::Result<File::Offset> File::seek(Offset new_position) {
|
||||
/* NOTE: Returns *old* position, not new position */
|
||||
const auto old_position = tell();
|
||||
|
||||
@@ -332,6 +332,7 @@ class File {
|
||||
|
||||
// TODO: Return Result<>.
|
||||
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true, bool create = false);
|
||||
void close();
|
||||
Optional<Error> append(const std::filesystem::path& filename);
|
||||
Optional<Error> create(const std::filesystem::path& filename);
|
||||
|
||||
@@ -342,6 +343,7 @@ class File {
|
||||
Result<Offset> seek(uint64_t Offset);
|
||||
Result<Offset> truncate();
|
||||
Size size() const;
|
||||
Result<bool> eof();
|
||||
|
||||
template <size_t N>
|
||||
Result<Size> write(const std::array<uint8_t, N>& data) {
|
||||
|
||||
@@ -144,6 +144,7 @@ Continuous (Fox-oring)
|
||||
|
||||
#include "rffc507x.hpp" /* c/m, avoiding initial short ON Ant_DC_Bias pulse, from cold reset */
|
||||
rffc507x::RFFC507x first_if;
|
||||
ui::SystemView* system_view_ptr;
|
||||
|
||||
static void event_loop() {
|
||||
static ui::Context context;
|
||||
@@ -151,6 +152,8 @@ static void event_loop() {
|
||||
context,
|
||||
portapack::display.screen_rect()};
|
||||
|
||||
system_view_ptr = &system_view;
|
||||
|
||||
EventDispatcher event_dispatcher{&system_view, context};
|
||||
static MessageHandlerRegistration message_handler_display_sleep{
|
||||
Message::ID::DisplaySleep,
|
||||
@@ -158,6 +161,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ using asahi_kasei::ak4951::AK4951;
|
||||
#include "sd_card.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
namespace portapack {
|
||||
|
||||
@@ -585,6 +586,7 @@ init_status_t init() {
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
audio::init(portapack_audio_codec());
|
||||
battery::BatteryManagement::init();
|
||||
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(4, ui::bitmap_icon_speaker);
|
||||
@@ -617,4 +619,6 @@ void setEventDispatcherToUSBSerial(EventDispatcher* evt) {
|
||||
usb_serial.setEventDispatcher(evt);
|
||||
}
|
||||
|
||||
bool async_tx_enabled = false; // this is for serial tx things, globally
|
||||
|
||||
} /* namespace portapack */
|
||||
|
||||
@@ -33,9 +33,12 @@
|
||||
#include "backlight.hpp"
|
||||
#include "usb_serial.hpp"
|
||||
|
||||
#include "ads1110.hpp"
|
||||
|
||||
#include "radio.hpp"
|
||||
#include "clock_manager.hpp"
|
||||
#include "temperature_logger.hpp"
|
||||
#include "battery.hpp"
|
||||
|
||||
/* TODO: This would be better as a class to add
|
||||
* guardrails on setting properties. */
|
||||
@@ -81,4 +84,6 @@ void setEventDispatcherToUSBSerial(EventDispatcher* evt);
|
||||
|
||||
Backlight* backlight();
|
||||
|
||||
extern bool async_tx_enabled; // this is for serial tx things, globally
|
||||
|
||||
} /* namespace portapack */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -159,6 +159,7 @@ std::string to_string_decimal(float decimal, int8_t precision) {
|
||||
double fractional_part;
|
||||
|
||||
std::string result;
|
||||
if (precision > 9) precision = 9; // we will convert to uin32_t, and that is the max it can hold.
|
||||
|
||||
fractional_part = modf(decimal, &integer_part) * pow(10, precision);
|
||||
|
||||
@@ -171,6 +172,31 @@ std::string to_string_decimal(float decimal, int8_t precision) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_string_decimal_padding(float decimal, int8_t precision, const int32_t l) {
|
||||
double integer_part;
|
||||
double fractional_part;
|
||||
|
||||
std::string result;
|
||||
if (precision > 9) precision = 9; // we will convert to uin32_t, and that is the max it can hold.
|
||||
|
||||
fractional_part = modf(decimal, &integer_part) * pow(10, precision);
|
||||
|
||||
if (fractional_part < 0) {
|
||||
fractional_part = -fractional_part;
|
||||
}
|
||||
|
||||
result = to_string_dec_int(integer_part) + "." + to_string_dec_uint(fractional_part, precision, '0');
|
||||
|
||||
// Add padding with spaces to meet the length requirement
|
||||
if (result.length() < (uint32_t)l) {
|
||||
int padding_length = l - result.length();
|
||||
std::string padding(padding_length, ' ');
|
||||
result = padding + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// right-justified frequency in Hz, always 10 characters
|
||||
std::string to_string_freq(const uint64_t f) {
|
||||
std::string final_str{""};
|
||||
|
||||
@@ -54,6 +54,7 @@ std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
|
||||
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
|
||||
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
|
||||
std::string to_string_decimal(float decimal, int8_t precision);
|
||||
std::string to_string_decimal_padding(float decimal, int8_t precision, const int32_t l);
|
||||
|
||||
std::string to_string_hex(uint64_t n, int32_t length);
|
||||
std::string to_string_hex_array(uint8_t* array, int32_t length);
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
#include "ui_bmpview.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
bool BMPViewer::load_bmp(const std::filesystem::path& file) {
|
||||
if (!bmp.open(file, true)) return false;
|
||||
// calc default zoom level to fit screen, and min / max zoom too
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
auto b_width = bmp.get_width();
|
||||
auto b_height = bmp.get_real_height();
|
||||
// aspects
|
||||
// if image is smaller then our vp
|
||||
auto x_w = d_width / b_width;
|
||||
auto x_h = d_height / b_height;
|
||||
if (x_w < 1 && x_h < 1) {
|
||||
// not zoom in, but zoom out
|
||||
x_w = b_width / d_width;
|
||||
x_h = b_height / d_height;
|
||||
x_w = (127 < x_w) ? 127 : x_w;
|
||||
x_h = (127 < x_h) ? 127 : x_h;
|
||||
zoom_fit = (x_h > x_w) ? -1 * x_h : -1 * x_w;
|
||||
} else {
|
||||
x_w = (127 < x_w) ? 127 : x_w;
|
||||
x_h = (127 < x_h) ? 127 : x_h;
|
||||
zoom_fit = (x_h > x_w) ? x_h : x_w;
|
||||
}
|
||||
if (zoom_fit > max_zoom) zoom_fit = max_zoom;
|
||||
min_zoom = zoom_fit - 3;
|
||||
|
||||
reset_pos();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPViewer::move_pos(int32_t delta_x, int32_t delta_y) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
auto ocx = cx; // save old pos
|
||||
auto ocy = cy;
|
||||
// top left protection
|
||||
if (delta_x < 0 && cx <= (uint32_t)(-1 * delta_x))
|
||||
cx = 0;
|
||||
else
|
||||
cx += delta_x;
|
||||
|
||||
if (delta_y < 0 && cy <= (uint32_t)(-1 * delta_y))
|
||||
cy = 0;
|
||||
else
|
||||
cy += delta_y;
|
||||
// right bottom protection
|
||||
float zt = zoom < 0 ? -1.0f / (float)zoom : (float)zoom;
|
||||
if (zt == 0) zt = 1;
|
||||
if (cy + (uint32_t)(d_height / zt) > bmp.get_real_height()) {
|
||||
cy = (bmp.get_real_height() < (uint32_t)(d_height / zt)) ? 0 : bmp.get_real_height() - (uint32_t)(d_height / zt);
|
||||
}
|
||||
if (cx + (uint32_t)(d_width / zt) > bmp.get_width()) {
|
||||
cx = (bmp.get_width() < (uint32_t)(d_width / zt)) ? 0 : bmp.get_width() - (uint32_t)(d_width / zt);
|
||||
}
|
||||
bool ret = !(cx == ocx && ocy == cy); // was any change?
|
||||
if (ret) set_dirty();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BMPViewer::set_zoom(int8_t new_zoom) {
|
||||
if (!bmp.is_loaded()) return;
|
||||
if (new_zoom > max_zoom) new_zoom = max_zoom;
|
||||
if (new_zoom < min_zoom) new_zoom = min_zoom;
|
||||
if (new_zoom == 0) new_zoom = 1;
|
||||
if (new_zoom == -1) new_zoom = 1;
|
||||
zoom = new_zoom;
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
if (zoom < 0) {
|
||||
mvx = d_width / 3 * (-1.0 * zoom);
|
||||
mvy = d_height / 3 * (-1.0 * zoom);
|
||||
} else {
|
||||
mvx = d_width / zoom / 3;
|
||||
mvy = d_height / zoom / 3;
|
||||
}
|
||||
move_pos(0, 0); // fix based on zoom, without real move (if not edge case)
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
// reads a lint from the bmp's bx, by coordinate to the line that's size is cnt. according to zoom
|
||||
void BMPViewer::get_line(ui::Color* line, uint32_t bx, uint32_t by, uint32_t cnt) {
|
||||
if (!bmp.is_loaded()) return;
|
||||
uint32_t last_targetx = 65534;
|
||||
for (uint32_t x = 0; x < cnt; x++) {
|
||||
uint32_t targetx = (zoom < 0) ? bx + x * -1 * zoom : bx + x / zoom; // on zoom out could probably avg the pixels, or apply some smoothing, but this is way faster.
|
||||
if (last_targetx == targetx) {
|
||||
line[x] = line[x - 1];
|
||||
continue;
|
||||
}
|
||||
last_targetx = targetx;
|
||||
if (!bmp.seek(targetx, by)) {
|
||||
line[x] = Color::white(); // can't seek there
|
||||
} else {
|
||||
bmp.read_next_px(line[x], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BMPViewer::paint(Painter& painter) {
|
||||
if (!bmp.is_loaded()) {
|
||||
painter.draw_string({48, 24}, ui::Styles::white, "Can't load BMP");
|
||||
return;
|
||||
}
|
||||
// get where i can paint
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
|
||||
uint32_t by = cy; // we start to read from there
|
||||
uint32_t last_by = 65534;
|
||||
ui::Color* line = new ui::Color[d_width];
|
||||
for (int32_t y = 0; y < d_height; y++) {
|
||||
by = cy + ((zoom < 0) ? y * -1 * zoom : y / (int32_t)zoom);
|
||||
if (by != last_by) get_line(line, cx, by, d_width);
|
||||
last_by = by;
|
||||
portapack::display.draw_pixels({rect.left(), rect.top() + y, d_width, 1}, line, d_width);
|
||||
}
|
||||
delete line;
|
||||
}
|
||||
|
||||
int8_t BMPViewer::get_zoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
BMPViewer::BMPViewer(Rect parent_rect)
|
||||
: Widget{parent_rect} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
BMPViewer::BMPViewer(Rect parent_rect, const std::filesystem::path& file)
|
||||
: Widget{parent_rect} {
|
||||
set_focusable(true);
|
||||
load_bmp(file);
|
||||
}
|
||||
|
||||
void BMPViewer::on_focus() {
|
||||
set_highlighted(true);
|
||||
}
|
||||
|
||||
void BMPViewer::on_blur() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BMPViewer::reset_pos() {
|
||||
if (!bmp.is_loaded()) return;
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
set_zoom(zoom_fit);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool BMPViewer::on_key(const KeyEvent key) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
if (key == KeyEvent::Up) {
|
||||
return move_pos(0, -1 * mvy);
|
||||
}
|
||||
if (key == KeyEvent::Down) {
|
||||
return move_pos(0, mvy);
|
||||
}
|
||||
if (key == KeyEvent::Left) {
|
||||
return move_pos(-1 * mvx, 0);
|
||||
}
|
||||
if (key == KeyEvent::Right) {
|
||||
return move_pos(mvx, 0);
|
||||
}
|
||||
if (key == KeyEvent::Select) {
|
||||
if (!enter_pass) {
|
||||
reset_pos();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BMPViewer::on_encoder(EncoderEvent delta) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
if (delta > 0) {
|
||||
set_zoom(zoom + delta); // 0 handled in set_zoom
|
||||
return true;
|
||||
}
|
||||
if (delta < 0) {
|
||||
if (zoom == 1) // not 0, but -1
|
||||
set_zoom(-2);
|
||||
else
|
||||
set_zoom(zoom + delta); // decrease
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// sets if the enter key should be passed to parent or handled. true = pass it, false = handle as reset pos+zoom
|
||||
void BMPViewer::set_enter_pass(bool pass) {
|
||||
enter_pass = pass;
|
||||
}
|
||||
|
||||
bool BMPViewer::get_enter_pass() {
|
||||
return enter_pass;
|
||||
}
|
||||
|
||||
bool BMPViewer::on_keyboard(const KeyboardEvent event) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
if (event == '+') {
|
||||
set_zoom(zoom + 1);
|
||||
return true;
|
||||
}
|
||||
if (event == '-') {
|
||||
if (zoom == 1) // not 0, but -1
|
||||
set_zoom(-2);
|
||||
else
|
||||
set_zoom(zoom - 1); // decrease
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UIBMPVIEW_H__
|
||||
#define __UIBMPVIEW_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "bmpfile.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
|
||||
class BMPViewer : public Widget {
|
||||
public:
|
||||
BMPViewer(Rect parent_rect);
|
||||
BMPViewer(Rect parent_rect, const std::filesystem::path& file);
|
||||
BMPViewer(const BMPViewer& other) = delete;
|
||||
BMPViewer& operator=(const BMPViewer& other) = delete;
|
||||
|
||||
bool load_bmp(const std::filesystem::path& file);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void on_focus() override;
|
||||
void on_blur() override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
|
||||
void reset_pos();
|
||||
void set_zoom(int8_t new_zoom);
|
||||
int8_t get_zoom();
|
||||
|
||||
void set_enter_pass(bool pass);
|
||||
bool get_enter_pass();
|
||||
|
||||
private:
|
||||
void get_line(ui::Color* line, uint32_t bx, uint32_t by, uint32_t cnt);
|
||||
bool move_pos(int32_t delta_x, int32_t delta_y);
|
||||
BMPFile bmp{};
|
||||
int8_t zoom = 1; // positive = zoom in, negative = zoom out 0-invalid 1- no zoom
|
||||
int8_t zoom_fit = 1; // if this value is set, the image will fit the screen the most
|
||||
int8_t max_zoom = 10;
|
||||
int8_t min_zoom = -20; // will be calculated on load
|
||||
uint32_t cx = 0; // current top-left coordinate
|
||||
uint32_t cy = 0;
|
||||
uint32_t mvx = 1; // how much to move on key
|
||||
uint32_t mvy = 1;
|
||||
bool enter_pass = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -4,6 +4,7 @@
|
||||
* Copyright (C) 2019 Elia Yehuda (z4ziggy)
|
||||
* Copyright (C) 2023 Mark Thompson
|
||||
* Copyright (C) 2024 u-foka
|
||||
* Copyleft (ↄ) 2024 zxkmm with the GPL license
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -133,6 +134,19 @@ void BtnGridView::add_item(GridItem new_item) {
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::insert_item(GridItem new_item, uint8_t position) {
|
||||
if (!blacklisted_app(new_item)) {
|
||||
if (position < menu_items.size()) {
|
||||
auto pos_iter = menu_items.begin() + position;
|
||||
menu_items.insert(pos_iter, new_item);
|
||||
update_items();
|
||||
} else {
|
||||
menu_items.push_back(new_item);
|
||||
update_items();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BtnGridView::update_items() {
|
||||
size_t i = 0;
|
||||
Color bg_color = portapack::persistent_memory::menu_color();
|
||||
|
||||
@@ -61,6 +61,7 @@ class BtnGridView : public View {
|
||||
|
||||
void add_items(std::initializer_list<GridItem> new_items);
|
||||
void add_item(GridItem new_item);
|
||||
void insert_item(GridItem new_item, uint8_t position);
|
||||
void set_max_rows(int rows);
|
||||
int rows();
|
||||
void clear();
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "sd_card.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "ui_standalone_view.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -10,8 +11,10 @@ namespace ui {
|
||||
// iterates over all ppma-s, and if it is runnable on the current system, it'll call the callback, and pass info.
|
||||
/* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback) {
|
||||
if (!callback) return;
|
||||
|
||||
if (sd_card::status() != sd_card::Status::Mounted)
|
||||
return;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppma")) {
|
||||
auto filePath = apps_dir / entry.path();
|
||||
File app;
|
||||
@@ -43,6 +46,37 @@ namespace ui {
|
||||
.appLocation = application_information.menu_location};
|
||||
callback(info);
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppmp")) {
|
||||
auto filePath = apps_dir / entry.path();
|
||||
File app;
|
||||
|
||||
auto openError = app.open(filePath);
|
||||
if (openError)
|
||||
continue;
|
||||
|
||||
standalone_application_information_t application_information = {};
|
||||
|
||||
auto readResult = app.read(&application_information, sizeof(standalone_application_information_t));
|
||||
if (!readResult)
|
||||
continue;
|
||||
|
||||
if (application_information.header_version < CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
continue;
|
||||
|
||||
// here the app is startable and good.
|
||||
std::string appshortname = filePath.filename().string();
|
||||
if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppmp") {
|
||||
// Remove the ".ppmp" suffix
|
||||
appshortname = appshortname.substr(0, appshortname.size() - 5);
|
||||
}
|
||||
AppInfoConsole info{
|
||||
.appCallName = appshortname.c_str(),
|
||||
.appFriendlyName = reinterpret_cast<char*>(&application_information.app_name[0]),
|
||||
.appLocation = application_information.menu_location};
|
||||
|
||||
callback(info);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ std::vector<GridItem> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
|
||||
@@ -103,6 +137,44 @@ namespace ui {
|
||||
external_apps.push_back(gridItem);
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(apps_dir, u"*.ppmp")) {
|
||||
auto filePath = apps_dir / entry.path();
|
||||
File app;
|
||||
|
||||
auto openError = app.open(filePath);
|
||||
if (openError)
|
||||
continue;
|
||||
|
||||
standalone_application_information_t application_information = {};
|
||||
|
||||
auto readResult = app.read(&application_information, sizeof(standalone_application_information_t));
|
||||
if (!readResult)
|
||||
continue;
|
||||
|
||||
if (application_information.menu_location != app_location)
|
||||
continue;
|
||||
|
||||
if (application_information.header_version > CURRENT_STANDALONE_APPLICATION_API_VERSION)
|
||||
continue;
|
||||
|
||||
GridItem gridItem = {};
|
||||
gridItem.text = reinterpret_cast<char*>(&application_information.app_name[0]);
|
||||
|
||||
gridItem.color = Color((uint16_t)application_information.icon_color);
|
||||
|
||||
auto dyn_bmp = DynamicBitmap<16, 16>{application_information.bitmap_data};
|
||||
gridItem.bitmap = dyn_bmp.bitmap();
|
||||
bitmaps.push_back(std::move(dyn_bmp));
|
||||
|
||||
gridItem.on_select = [&nav, app_location, filePath]() {
|
||||
if (!run_standalone_app(nav, filePath)) {
|
||||
nav.display_modal("Error", "The .ppmp file in your " + apps_dir.string() + "\nfolder can't be read. Please\nupdate your SD Card content.");
|
||||
}
|
||||
};
|
||||
|
||||
external_apps.push_back(gridItem);
|
||||
}
|
||||
|
||||
return external_apps;
|
||||
}
|
||||
|
||||
@@ -187,4 +259,39 @@ namespace ui {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool ExternalItemsMenuLoader::run_standalone_app(ui::NavigationView& nav, std::filesystem::path filePath) {
|
||||
File app;
|
||||
|
||||
auto openError = app.open(filePath);
|
||||
if (openError)
|
||||
return false;
|
||||
|
||||
auto app_image = std::make_unique<uint8_t[]>(app.size());
|
||||
|
||||
// read file in 512 byte chunks
|
||||
for (size_t file_read_index = 0; file_read_index < app.size(); file_read_index += std::filesystem::max_file_block_size) {
|
||||
auto bytes_to_read = std::filesystem::max_file_block_size;
|
||||
if (file_read_index + std::filesystem::max_file_block_size > app.size())
|
||||
bytes_to_read = app.size() - file_read_index;
|
||||
|
||||
auto readResult = app.read(&app_image[file_read_index], bytes_to_read);
|
||||
if (!readResult)
|
||||
return false;
|
||||
|
||||
if (readResult.value() < std::filesystem::max_file_block_size)
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t file_read_index = 0; file_read_index < app.size() / 4; file_read_index++) {
|
||||
uint32_t* ptr = reinterpret_cast<uint32_t*>(&app_image[file_read_index * 4]);
|
||||
|
||||
if (*ptr >= 0xADB10000 && *ptr < (0xADB10000 + 64 * 1024)) {
|
||||
*ptr = *ptr - 0xADB10000 + (uint32_t)app_image.get();
|
||||
}
|
||||
}
|
||||
|
||||
nav.push<StandaloneView>(std::move(app_image));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
#include "standalone_app.hpp"
|
||||
|
||||
#include "file.hpp"
|
||||
|
||||
@@ -57,6 +58,7 @@ class ExternalItemsMenuLoader {
|
||||
static std::vector<GridItem> load_external_items(app_location_t, NavigationView&);
|
||||
ExternalItemsMenuLoader() = delete;
|
||||
static bool run_external_app(ui::NavigationView&, std::filesystem::path);
|
||||
static bool run_standalone_app(ui::NavigationView&, std::filesystem::path);
|
||||
static void load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback);
|
||||
|
||||
private:
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
#include "ui_weatherstation.hpp"
|
||||
#include "ui_subghzd.hpp"
|
||||
#include "ui_whipcalc.hpp"
|
||||
#include "ui_battinfo.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
// #include "acars_app.hpp"
|
||||
@@ -100,13 +101,17 @@
|
||||
#include "pocsag_app.hpp"
|
||||
#include "replay_app.hpp"
|
||||
#include "soundboard_app.hpp"
|
||||
#include "tpms_app.hpp"
|
||||
// #include "tpms_app.hpp" //moved to ext
|
||||
|
||||
#include "core_control.hpp"
|
||||
#include "file.hpp"
|
||||
#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;
|
||||
@@ -167,9 +172,9 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"tmps", "TPMS Cars", RX, Color::green(), &bitmap_icon_tpms, new ViewFactory<TPMSAppView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
//{"tmps", "TPMS Cars", RX, Color::green(), &bitmap_icon_tpms, new ViewFactory<TPMSAppView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
//{"fskrx", "FSK RX", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<FskxRxMainView>()},
|
||||
//{"dmr", "DMR", RX, Color::dark_grey(), &bitmap_icon_dmr, new ViewFactory<NotImplementedView>()},
|
||||
//{"sigfox", "SIGFOX", RX, Color::dark_grey(), &bitmap_icon_fox, new ViewFactory<NotImplementedView>()},
|
||||
@@ -330,6 +335,9 @@ SystemStatusView::SystemStatusView(
|
||||
refresh();
|
||||
};
|
||||
|
||||
battery_icon.on_select = [this]() { on_battery_details(); };
|
||||
battery_text.on_select = [this]() { on_battery_details(); };
|
||||
|
||||
button_fake_brightness.on_select = [this](ImageButton&) {
|
||||
set_dirty();
|
||||
pmem::toggle_fake_brightness_level();
|
||||
@@ -366,6 +374,26 @@ SystemStatusView::SystemStatusView(
|
||||
refresh();
|
||||
}
|
||||
|
||||
// when battery icon / text is clicked
|
||||
void SystemStatusView::on_battery_details() {
|
||||
if (!nav_.is_valid()) return;
|
||||
if (batt_info_up) return;
|
||||
batt_info_up = true;
|
||||
nav_.push<BattinfoView>();
|
||||
nav_.set_on_pop([this]() {
|
||||
batt_info_up = false;
|
||||
});
|
||||
}
|
||||
|
||||
void SystemStatusView::on_battery_data(const BatteryStateMessage* msg) {
|
||||
if (!pmem::ui_hide_numeric_battery()) {
|
||||
battery_text.set_battery(msg->percent, msg->on_charger);
|
||||
}
|
||||
if (!pmem::ui_hide_battery_icon()) {
|
||||
battery_icon.set_battery(msg->percent, msg->on_charger);
|
||||
};
|
||||
}
|
||||
|
||||
void SystemStatusView::refresh() {
|
||||
// NB: Order of insertion is the display order Left->Right.
|
||||
// TODO: Might be better to support hide and only add once.
|
||||
@@ -382,8 +410,20 @@ void SystemStatusView::refresh() {
|
||||
if (audio::speaker_disable_supported() && !pmem::ui_hide_speaker()) status_icons.add(&toggle_speaker);
|
||||
|
||||
if (!pmem::ui_hide_fake_brightness()) status_icons.add(&button_fake_brightness);
|
||||
if (battery::BatteryManagement::isDetected()) {
|
||||
uint8_t percent = battery::BatteryManagement::getPercent();
|
||||
if (!pmem::ui_hide_battery_icon()) {
|
||||
status_icons.add(&battery_icon);
|
||||
battery_text.set_battery(percent, false); // got an on select, that may pop up the details of the battery.
|
||||
};
|
||||
if (!pmem::ui_hide_numeric_battery()) {
|
||||
status_icons.add(&battery_text);
|
||||
battery_text.set_battery(percent, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pmem::ui_hide_sd_card()) status_icons.add(&sd_card_status_view);
|
||||
|
||||
status_icons.update_layout();
|
||||
|
||||
// Clock status
|
||||
@@ -418,6 +458,10 @@ void SystemStatusView::set_back_enabled(bool new_value) {
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStatusView::set_back_hidden(bool new_value) {
|
||||
button_back.hidden(new_value);
|
||||
}
|
||||
|
||||
void SystemStatusView::set_title_image_enabled(bool new_value) {
|
||||
if (new_value) {
|
||||
add_child(&button_title);
|
||||
@@ -659,8 +703,9 @@ void NavigationView::display_modal(
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
modal_t type,
|
||||
std::function<void(bool)> on_choice) {
|
||||
push<ModalMessageView>(title, message, type, on_choice);
|
||||
std::function<void(bool)> on_choice,
|
||||
bool compact) {
|
||||
push<ModalMessageView>(title, message, type, on_choice, compact);
|
||||
}
|
||||
|
||||
void NavigationView::free_view() {
|
||||
@@ -708,6 +753,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) {
|
||||
@@ -723,16 +785,17 @@ static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc)
|
||||
void addExternalItems(NavigationView& nav, app_location_t location, BtnGridView& grid) {
|
||||
auto externalItems = ExternalItemsMenuLoader::load_external_items(location, nav);
|
||||
if (externalItems.empty()) {
|
||||
grid.add_item({"Notice",
|
||||
Color::red(),
|
||||
&bitmap_icon_debug,
|
||||
[&nav]() {
|
||||
nav.display_modal(
|
||||
"Notice",
|
||||
"External app directory empty;\n"
|
||||
"see Mayhem wiki and copy apps\n"
|
||||
"to " + apps_dir.string() + " folder of SD card.");
|
||||
}});
|
||||
grid.insert_item({"Notice!",
|
||||
Color::red(),
|
||||
nullptr,
|
||||
[&nav]() {
|
||||
nav.display_modal(
|
||||
"Notice",
|
||||
"External app directory empty;\n"
|
||||
"see Mayhem wiki and copy apps\n"
|
||||
"to " + apps_dir.string() + " folder of SD card.");
|
||||
}},
|
||||
pmem::show_gui_return_icon() ? 1 : 0);
|
||||
} else {
|
||||
for (auto const& gridItem : externalItems) {
|
||||
grid.add_item(gridItem);
|
||||
@@ -874,6 +937,9 @@ Context& SystemView::context() const {
|
||||
NavigationView* SystemView::get_navigation_view() {
|
||||
return &navigation_view;
|
||||
}
|
||||
SystemStatusView* SystemView::get_status_view() {
|
||||
return &status_view;
|
||||
}
|
||||
|
||||
void SystemView::toggle_overlay() {
|
||||
static uint8_t last_perf_counter_status = shared_memory.request_m4_performance_counter;
|
||||
@@ -916,6 +982,15 @@ void SystemView::paint_overlay() {
|
||||
}
|
||||
}
|
||||
|
||||
void SystemView::set_app_fullscreen(bool fullscreen) {
|
||||
auto parent_rect = screen_rect();
|
||||
Dim status_view_height = (fullscreen) ? 0 : 16;
|
||||
status_view.hidden(fullscreen);
|
||||
navigation_view.set_parent_rect(
|
||||
{{0, status_view_height},
|
||||
{parent_rect.width(), static_cast<Dim>(parent_rect.height() - status_view_height)}});
|
||||
}
|
||||
|
||||
/* ***********************************************************************/
|
||||
|
||||
void BMPView::focus() {
|
||||
@@ -959,11 +1034,13 @@ ModalMessageView::ModalMessageView(
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
modal_t type,
|
||||
std::function<void(bool)> on_choice)
|
||||
std::function<void(bool)> on_choice,
|
||||
bool compact)
|
||||
: title_{title},
|
||||
message_{message},
|
||||
type_{type},
|
||||
on_choice_{on_choice} {
|
||||
on_choice_{on_choice},
|
||||
compact{compact} {
|
||||
if (type == INFO) {
|
||||
add_child(&button_ok);
|
||||
button_ok.on_select = [this, &nav](Button&) {
|
||||
@@ -996,13 +1073,13 @@ ModalMessageView::ModalMessageView(
|
||||
}
|
||||
|
||||
void ModalMessageView::paint(Painter& painter) {
|
||||
portapack::display.drawBMP({100, 48}, modal_warning_bmp, false);
|
||||
if (!compact) portapack::display.drawBMP({100, 48}, modal_warning_bmp, false);
|
||||
|
||||
// Break lines.
|
||||
auto lines = split_string(message_, '\n');
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
painter.draw_string(
|
||||
{1 * 8, (Coord)(120 + (i * 16))},
|
||||
{1 * 8, (Coord)(((compact) ? 8 * 3 : 120) + (i * 16))},
|
||||
style(),
|
||||
lines[i]);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,8 @@ class NavigationView : public View {
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
modal_t type,
|
||||
std::function<void(bool)> on_choice = nullptr);
|
||||
std::function<void(bool)> on_choice = nullptr,
|
||||
bool compact = false);
|
||||
|
||||
void focus() override;
|
||||
|
||||
@@ -136,6 +137,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;
|
||||
@@ -183,12 +186,13 @@ class SystemStatusView : public View {
|
||||
SystemStatusView(NavigationView& nav);
|
||||
|
||||
void set_back_enabled(bool new_value);
|
||||
void set_back_hidden(bool new_value);
|
||||
void set_title_image_enabled(bool new_value);
|
||||
void set_title(const std::string new_value);
|
||||
|
||||
private:
|
||||
static constexpr auto default_title = "";
|
||||
|
||||
bool batt_info_up = false; // to prevent show multiple batt info dialog
|
||||
NavigationView& nav_;
|
||||
|
||||
Rectangle backdrop{
|
||||
@@ -275,6 +279,9 @@ class SystemStatusView : public View {
|
||||
SDCardStatusView sd_card_status_view{
|
||||
{0, 0 * 16, 2 * 8, 1 * 16}};
|
||||
|
||||
BatteryTextField battery_text{{0, 0, 2 * 8, 1 * 16}, 102};
|
||||
BatteryIcon battery_icon{{0, 0, 10, 1 * 16}, 102};
|
||||
|
||||
void on_converter();
|
||||
void on_bias_tee();
|
||||
void on_camera();
|
||||
@@ -282,6 +289,8 @@ class SystemStatusView : public View {
|
||||
void refresh();
|
||||
void on_clk();
|
||||
void rtc_battery_workaround();
|
||||
void on_battery_data(const BatteryStateMessage* msg);
|
||||
void on_battery_details();
|
||||
|
||||
MessageHandlerRegistration message_handler_refresh{
|
||||
Message::ID::StatusRefresh,
|
||||
@@ -289,6 +298,13 @@ class SystemStatusView : public View {
|
||||
(void)p;
|
||||
this->refresh();
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_battery{
|
||||
Message::ID::BatteryStateData,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const BatteryStateMessage*>(p);
|
||||
this->on_battery_data(message);
|
||||
}};
|
||||
};
|
||||
|
||||
class InformationView : public View {
|
||||
@@ -374,8 +390,10 @@ class SystemView : public View {
|
||||
Context& context() const override;
|
||||
void toggle_overlay();
|
||||
void paint_overlay();
|
||||
void set_app_fullscreen(bool fullscreen);
|
||||
|
||||
NavigationView* get_navigation_view();
|
||||
SystemStatusView* get_status_view();
|
||||
|
||||
private:
|
||||
uint8_t overlay_active{0};
|
||||
@@ -413,7 +431,8 @@ class ModalMessageView : public View {
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
modal_t type,
|
||||
std::function<void(bool)> on_choice);
|
||||
std::function<void(bool)> on_choice,
|
||||
bool compact = false);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void focus() override;
|
||||
@@ -425,6 +444,7 @@ class ModalMessageView : public View {
|
||||
const std::string message_;
|
||||
const modal_t type_;
|
||||
const std::function<void(bool)> on_choice_;
|
||||
const bool compact;
|
||||
|
||||
Button button_ok{
|
||||
{10 * 8, 14 * 16, 10 * 8, 48},
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyleft (ɔ) 2024 zxkmm with the GPL license
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usb_serial_asyncmsg.hpp"
|
||||
|
||||
/// value
|
||||
// to_string_bin/ to_string_decimal/ to_string_hex/ to_string_hex_array/ to_string_dec_uint/ to_string_dec_int etc seems usellss so i didn't add them here
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg(num);
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<int64_t>(const int64_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<int32_t>(const int32_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<int16_t>(const int16_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<int8_t>(const int8_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<uint8_t>(const uint8_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<uint16_t>(const uint16_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<uint32_t>(const uint32_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<uint64_t>(const uint64_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<float>(const float& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_decimal(data, 7).c_str());
|
||||
}
|
||||
|
||||
/// fs things
|
||||
|
||||
template <>
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg(path);
|
||||
void UsbSerialAsyncmsg::asyncmsg<std::filesystem::path>(const std::filesystem::path& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
std::string path_str = data.string();
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", path_str.c_str());
|
||||
}
|
||||
|
||||
/// string
|
||||
|
||||
// string obj
|
||||
template <>
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg(str);
|
||||
void UsbSerialAsyncmsg::asyncmsg<std::string>(const std::string& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", data.c_str());
|
||||
}
|
||||
|
||||
// string literal AKA char[]
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg("abc");
|
||||
void UsbSerialAsyncmsg::asyncmsg(const char* data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", data);
|
||||
}
|
||||
|
||||
/// bool
|
||||
template <>
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg(true);
|
||||
void UsbSerialAsyncmsg::asyncmsg<bool>(const bool& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", data ? "true" : "false");
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyleft (ɔ) 2024 zxkmm with the GPL license
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef USB_SERIAL_AYNCMSG_HPP
|
||||
#define USB_SERIAL_AYNCMSG_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "ch.h"
|
||||
#include "chprintf.h"
|
||||
#include "hal.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
class UsbSerialAsyncmsg {
|
||||
public:
|
||||
template <typename STRINGCOVER>
|
||||
static void asyncmsg(const STRINGCOVER& data);
|
||||
|
||||
template <typename VECTORCOVER>
|
||||
static void asyncmsg(const std::vector<VECTORCOVER>& data);
|
||||
|
||||
static void asyncmsg(const char* data); // string literal
|
||||
};
|
||||
|
||||
/*Notes:
|
||||
* - Don't use MayhemHub since it currently not support real time serial output
|
||||
* - If you don't use this class linker will drop it so it won't use any space
|
||||
* - so delete all debug things before you push your code to production
|
||||
* - use this client to filter only PP devices: https://github.com/zxkmm/Pyserial-Demo-portapack
|
||||
* - usage:
|
||||
* portapack::async_tx_enabled = true; // note that use this when debugging, unless the msg would be forbidden. but don't use this in production, since it's not real async and multiple serial transmittions will broken each other. if this class is used in other scene in the future, just use command to cover (protect your serial tramsnitton) in your extern thing: asyncmsg enable --- your cmd --- asyncmsg disable
|
||||
* #include "usb_serial_asyncmsg.hpp"
|
||||
* UsbSerialAsyncmsg::asyncmsg("Hello PP");
|
||||
* */
|
||||
|
||||
/// vec worker
|
||||
// ussgae: UsbSerialAsyncmsg::asyncmsg(vec);
|
||||
template <typename VECTORCOVER>
|
||||
void UsbSerialAsyncmsg::asyncmsg(const std::vector<VECTORCOVER>& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
for (const auto& item : data) {
|
||||
asyncmsg(item);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USB_SERIAL_AYNCMSG_HPP
|
||||
@@ -1053,6 +1053,101 @@ 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 void cmd_asyncmsg(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: asyncmsg x, x can be enable or disable\r\n";
|
||||
if (argc != 1) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
if (strcmp(argv[0], "disable") == 0) {
|
||||
portapack::async_tx_enabled = false;
|
||||
chprintf(chp, "ok\r\n");
|
||||
} else if (strcmp(argv[0], "enable") == 0) {
|
||||
portapack::async_tx_enabled = true;
|
||||
chprintf(chp, "ok\r\n");
|
||||
} else {
|
||||
chprintf(chp, usage);
|
||||
}
|
||||
}
|
||||
|
||||
static const ShellCommand commands[] = {
|
||||
{"reboot", cmd_reboot},
|
||||
{"dfu", cmd_dfu},
|
||||
@@ -1083,6 +1178,8 @@ static const ShellCommand commands[] = {
|
||||
{"radioinfo", cmd_radioinfo},
|
||||
{"pmemreset", cmd_pmemreset},
|
||||
{"settingsreset", cmd_settingsreset},
|
||||
{"sendpocsag", cmd_sendpocsag},
|
||||
{"asyncmsg", cmd_asyncmsg},
|
||||
{NULL, NULL}};
|
||||
|
||||
static const ShellConfig shell_cfg1 = {
|
||||
|
||||
@@ -498,12 +498,6 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PTON tones)
|
||||
|
||||
### TPMS
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_tpms.cpp
|
||||
)
|
||||
DeclareTargets(PTPM tpms)
|
||||
|
||||
### Wideband Spectrum
|
||||
|
||||
@@ -651,6 +645,15 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PABP audio_beep)
|
||||
|
||||
|
||||
### TPMS
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_tpms.cpp
|
||||
)
|
||||
DeclareTargets(PTPM tpms)
|
||||
|
||||
|
||||
### HackRF "factory" firmware
|
||||
|
||||
add_custom_command(
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "sine_table_int8.hpp"
|
||||
#include "event_m4.hpp"
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -144,16 +145,30 @@ void ADSBRXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
}
|
||||
|
||||
void ADSBRXProcessor::on_message(const Message* const message) {
|
||||
if (message->id == Message::ID::ADSBConfigure) {
|
||||
bit_count = 0;
|
||||
sample_count = 0;
|
||||
decoding = false;
|
||||
configured = true;
|
||||
switch (message->id) {
|
||||
case Message::ID::ADSBConfigure:
|
||||
bit_count = 0;
|
||||
sample_count = 0;
|
||||
decoding = false;
|
||||
configured = true;
|
||||
break;
|
||||
|
||||
case Message::ID::AudioBeep:
|
||||
on_beep_message(*reinterpret_cast<const AudioBeepMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBRXProcessor::on_beep_message(const AudioBeepMessage& message) {
|
||||
audio::dma::beep_start(message.freq, message.sample_rate, message.duration_ms);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
int main() {
|
||||
audio::dma::init_audio_out();
|
||||
EventDispatcher event_dispatcher{std::make_unique<ADSBRXProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
|
||||
@@ -53,6 +53,8 @@ class ADSBRXProcessor : public BasebandProcessor {
|
||||
|
||||
uint32_t shifter[ADSB_PREAMBLE_LENGTH + 1];
|
||||
|
||||
void on_beep_message(const AudioBeepMessage& message);
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "proc_ais.hpp"
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
@@ -64,7 +65,17 @@ void AISProcessor::payload_handler(
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
void AISProcessor::on_message(const Message* const message) {
|
||||
if (message->id == Message::ID::AudioBeep)
|
||||
on_beep_message(*reinterpret_cast<const AudioBeepMessage*>(message));
|
||||
}
|
||||
|
||||
void AISProcessor::on_beep_message(const AudioBeepMessage& message) {
|
||||
audio::dma::beep_start(message.freq, message.sample_rate, message.duration_ms);
|
||||
}
|
||||
|
||||
int main() {
|
||||
audio::dma::init_audio_out();
|
||||
EventDispatcher event_dispatcher{std::make_unique<AISProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
|
||||
@@ -74,13 +74,15 @@ class AISProcessor : public BasebandProcessor {
|
||||
this->payload_handler(packet);
|
||||
}};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
void on_message(const Message* const message);
|
||||
void on_beep_message(const AudioBeepMessage& message);
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{
|
||||
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
void consume_symbol(const float symbol);
|
||||
void payload_handler(const baseband::Packet& packet);
|
||||
};
|
||||
|
||||
#endif /*__PROC_AIS_H__*/
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
#include "baseband_processor.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
#define AUDIO_SAMPLE_RATE 24000
|
||||
|
||||
class AudioBeepProcessor : public BasebandProcessor {
|
||||
public:
|
||||
AudioBeepProcessor();
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "proc_ert.hpp"
|
||||
#include "audio_dma.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
@@ -109,7 +110,17 @@ void ERTProcessor::idm_handler(
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
void ERTProcessor::on_message(const Message* const msg) {
|
||||
if (msg->id == Message::ID::AudioBeep)
|
||||
on_beep_message(*reinterpret_cast<const AudioBeepMessage*>(msg));
|
||||
}
|
||||
|
||||
void ERTProcessor::on_beep_message(const AudioBeepMessage& message) {
|
||||
audio::dma::beep_start(message.freq, message.sample_rate, message.duration_ms);
|
||||
}
|
||||
|
||||
int main() {
|
||||
audio::dma::init_audio_out();
|
||||
EventDispatcher event_dispatcher{std::make_unique<ERTProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
|
||||
@@ -102,6 +102,8 @@ class ERTProcessor : public BasebandProcessor {
|
||||
void scm_handler(const baseband::Packet& packet);
|
||||
void scmplus_handler(const baseband::Packet& packet);
|
||||
void idm_handler(const baseband::Packet& packet);
|
||||
void on_message(const Message* const msg);
|
||||
void on_beep_message(const AudioBeepMessage& message);
|
||||
|
||||
float sum_half_period[2];
|
||||
float sum_period[3];
|
||||
|
||||
@@ -361,6 +361,10 @@ void POCSAGProcessor::on_message(const Message* const message) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Message::ID::AudioBeep:
|
||||
on_beep_message(*reinterpret_cast<const AudioBeepMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -414,6 +418,10 @@ void POCSAGProcessor::send_packet() {
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
void POCSAGProcessor::on_beep_message(const AudioBeepMessage& message) {
|
||||
audio::dma::beep_start(message.freq, message.sample_rate, message.duration_ms);
|
||||
}
|
||||
|
||||
/* main **************************************************/
|
||||
|
||||
int main() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user