Compare commits

..

36 Commits

Author SHA1 Message Date
Brumi-2021 a1496c1b79 Allowing to hear Mic at TX time with Receiver ON (#1522) 2023-10-22 01:13:32 +02:00
Brumi-2021 1b73a138b6 Mic to hp refactoring (#1521)
* Refactoring previous "Hear Mic" PR

* Missed file
2023-10-21 17:31:17 +02:00
Brumi-2021 efcedd9005 Add "Hear Mic" feature to the Mic App (#1518)
* Add "Hear Mic" feature to the Mic App

* Following consensus change about ternary operators
2023-10-20 18:43:22 -05:00
Mark Thompson 86d4b17257 Simple amplification option in IQ Trim app (#1506)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-10-19 20:14:25 +02:00
Mark Thompson f6a437f7fb Create SETTINGS folder if one does not exist (#1512)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-10-19 20:09:49 +02:00
jLynx 3db8a5ac5b Stable v1.8.0 release (#1504)
* Update version.txt

* Update past_version.txt
2023-10-17 15:20:40 +13:00
Mark Thompson 266a626acf POCSAG TX default freq (#1503)
* Changed name for blacklist
* Set default POCSAG TX freq to same as in POCSAG RX
2023-10-16 21:32:07 +02:00
Mark Thompson 411e2fb93a Hide unwanted apps by naming them in SETTINGS/blacklist file (#1502)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Changed some app name strings for blacklist
2023-10-15 18:04:26 -05:00
Mark Thompson 6ed83c08e3 Renamed .ini file for Burger Pager app (#1500) 2023-10-14 21:08:39 +02:00
Mark Thompson 9929376acf Added Freq to Radio State, only update specified App Settings, always Load/Save Settings (#1498)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-10-14 11:33:52 -05:00
Mark Thompson 5135b6404d Prevent unnecessary CPLD SRAM load on first RX operation (#1494) 2023-10-14 10:28:44 +02:00
Mark Thompson 790669c242 Added Transmitter settings to 2nd DFU overlay screen (#1497)
* Added Transmitter settings to 2nd DFU overlay
2023-10-14 10:25:40 +02:00
Brumi-2021 4bb8b1882c Error correction. Now comments matching with value (#1496) 2023-10-12 19:56:27 +02:00
Brumi-2021 56702ca7c2 Applying a proper TX_LPF to all remaining TX App (#1495) 2023-10-12 18:02:38 +02:00
Erwin Ried 9be4b9596d Update README.md (#1491)
Changing the introductory video with a newer one with a guy with weird danish accented english
2023-10-12 07:57:34 +02:00
Mark Thompson 37c6b35fb7 Moved external Font Viewer app to DEBUG menu (#1485)
* Updated icon
* Support external apps on DEBUG menu screen
2023-10-11 18:32:01 +02:00
Mark Thompson 5cd7a09797 Disable display scroll before showing fault debug message (#1482) 2023-10-11 07:45:45 +02:00
Mark Thompson a1856b9bb0 Fix some Guru Meditation Faults in ADSB-RX app (#1483)
* Don't use screen_rect in functions called from rtc_timer_tick
* Pause AircraftRecentEntry list changes when AC Details screen is displayed
2023-10-11 07:44:56 +02:00
Mark Thompson 29a36c8658 Revived Fonts Viewer as an External app (in Utilities menu) (#1484)
* Add files via upload
* Moved Font Viewer to external app
2023-10-11 07:43:56 +02:00
gullradriel 264b1b38ba fix complicated timing logic (#1480) 2023-10-09 20:01:40 +02:00
Mark Thompson 65e71508ac Incrementing date workaround for dead RTC battery (#1479)
* Enable f_utime function
* Added file_update_date() function
* Added rtc_battery_workaround() function
* Update ui_navigation.cpp
2023-10-09 12:04:46 +02:00
Mark Thompson e5546159c5 Kludge fix for null parent() (#1477) 2023-10-09 12:03:11 +02:00
gullradriel d7c961ea4f Fixing empty output file/store button/text display (#1475) 2023-10-08 22:21:00 +02:00
Mark Thompson ad987f2b5e Added new icon for IQ Trim, and IQ Trim Button in FileMan (#1476)
* Add files via upload

* Added trim icon

* Added trim icon

* Added Trim button to FileMan

* Added Trim button to FileMan

* Added Trim button to FileMan

* Added Trim button to FileMan
2023-10-08 14:57:59 -05:00
Brumi-2021 defb934c3f Activating TX Low Pass Filter in Second IF IC (#1470)
* Activating TX Low Pass Filter in Second IF IC
* solving PR conversation revisions
* Final TX LPF decision for ADSB_TX
* Small comments correction.
* Added set TX LPF also  in APRS, BurgerPgr,Jammer
2023-10-08 17:50:31 +02:00
Bernd Herzog 5a850984b9 Calculator app (#1474)
* implemented calculator app

* improved output

* refactoring
2023-10-08 16:24:02 +02:00
Bernd Herzog 1185b67b28 changed alignment for text sections (#1473) 2023-10-07 15:30:43 +02:00
Bernd Herzog abb42e8df4 added executeable bit for new scripts (#1471) 2023-10-03 08:17:45 +02:00
Bernd Herzog 7fdb1af69d External apps (#1469)
* implemented external app build

* added some ui stuff for testing

* added pacman game

* wired key to pacman game

* fixed pacman drawing issue

* changed afsk rx app to be external

* fixed ui::NavigationView initialization for external apps

* refactoring

* refactoring

* moved m4 image to external app

* added script for external app deployment

* refactoring

* implemented dynamic app listing

* added color to app icon

* improved app loading

* added external apps to sd card content

* refactoring

* review findings

* typo

* review findings

* improved memory management of bitmaps
2023-10-02 20:19:22 +02:00
Kyle Reed 78713cc2af Search cleanup, more binder support (#1467) 2023-10-01 09:04:37 -07:00
Kyle Reed 951890eaff Add Binder helper methods (#1465)
* Add binder, fix focus on remote btn delete
* Use binder for freqman edit
2023-09-29 18:55:27 +02:00
Kyle Reed fb00bfac3f Nav stack cleanup (#1460)
* Minor nav stack fixes
* Nav stack cleanup
* Additional cleanup, fix notepad crash
* Fix abort/cancel
* Fix for nasty focus bug
* Format
2023-09-27 21:03:02 +02:00
Kyle Reed a6a1483083 Improved trimming (#1458)
* Add threshold UI
* WIP Better trimming
* Rewrite mostly done WIP
* WIP - trim idea
* WIP threshold trimming
* WIP with new design
* Cleanup
2023-09-23 21:56:37 +02:00
Kyle Reed ef03f020ce IQ Trim app and Capture auto-trim (#1456)
* Add IQ Trim app WIP
* Add 'trim' checkbox to capture
* Implement trimming
* Finish auto-trim
* Stray //
2023-09-21 17:43:10 +02:00
Kyle Reed 070eb4003e Remove proc pocsag & add keep/drop filtering (#1453)
* Remove proc_pocsag from build, use new proc in app
* Add support for keep/drop filtering
2023-09-19 11:46:30 +02:00
Kyle Reed fca373d936 Add Remote App & UI updates. (#1451)
* Alpha order sub-menus
* WIP Getting Remote types outlined
* WIP building UI
* WIP adding RemoteButton control
* WIP Fix build
* WIP Basic editing support
* Border on the active button
* Make TxView2 sane
* Add easier RGB color creation from uint32
* Center some button icons
* WIP Remote - main UI
* WIP main UI mostly working, can send
* Add 'join' utility
* WIP save/load
* Pre-alloc buttons to prevent focus dangling
* Alpha order settings/debug pages
* Add UI for picking capture and set frequency
* WIP Getting really close now
* Fix path for init name
* Some fit & finish
2023-09-18 23:22:46 +02:00
173 changed files with 15788 additions and 1258 deletions
+2 -2
View File
@@ -49,7 +49,7 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@@ -61,7 +61,7 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
+2 -2
View File
@@ -33,7 +33,7 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
@@ -45,7 +45,7 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
+1 -1
View File
@@ -1 +1 @@
v1.7.3
v1.7.4
+1 -1
View File
@@ -1 +1 @@
v1.7.4
v1.8.0
+6
View File
@@ -42,6 +42,12 @@ if ("$ENV{VERSION_STRING}" STREQUAL "")
endif (GIT_VERSION_FOUND)
endif()
execute_process(
COMMAND bash "-c" "echo ${VERSION} | md5sum | head -c 8"
OUTPUT_VARIABLE VERSION_MD5
)
set(VERSION_MD5 "0x${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
+3 -3
View File
@@ -8,11 +8,11 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure)*
# Quick overview
# What is this?
If you are new to *HackRF+PortaPack+Mayhem*, there is an awesome introductory video by [Tech Minds](https://www.youtube.com/channel/UC9a8Z6Sp6eb2s3O79pX5Zvg) available:
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
[![Setup and overview](https://img.youtube.com/vi/kjFB58Y1TAo/0.jpg)](https://www.youtube.com/watch?v=kjFB58Y1TAo)
[![What is?](https://img.youtube.com/vi/alrFbY5vxt4/0.jpg)](https://www.youtube.com/watch?v=alrFbY5vxt4)
# Frequently Asked Questions
+10 -2
View File
@@ -26,6 +26,7 @@ set(CHIBIOS ${PROJECT_SOURCE_DIR}/chibios)
set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack)
set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py)
set(EXPORT_EXTERNAL_APP_IMAGES ${PROJECT_SOURCE_DIR}/tools/export_external_apps.py)
set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
set(LZ4 lz4)
@@ -55,8 +56,15 @@ add_custom_target(
program
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE} || (exit 0) # We need to add it for dfu-utils v.011 , (in v.09 it is not necessary)
COMMAND sleep 3s
COMMAND hackrf_spiflash -w ${FIRMWARE_FILENAME}
DEPENDS ${FIRMWARE_FILENAME}
COMMAND hackrf_spiflash -i -R -w ${FIRMWARE_FILENAME}
DEPENDS firmware ${FIRMWARE_FILENAME}
)
add_custom_target(
program-external-apps
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${PROJECT_SOURCE_DIR}/tools/copy_external_apps.sh
DEPENDS program
)
# TODO: Bad hack to fix location of LICENSE file for tar.
+39 -33
View File
@@ -32,7 +32,7 @@ include(CheckCXXCompilerFlag)
project(application)
# Compiler options here.
set(USE_OPT "-Os -g --specs=nano.specs")
set(USE_OPT "-Os -g --specs=nano.specs -specs=nosys.specs")
# C specific options here (added to USE_OPT).
set(USE_COPT "-std=gnu99")
@@ -97,6 +97,7 @@ include(${CHIBIOS_PORTAPACK}/os/ports/GCC/ARMCMx/LPC43xx_M0/port.cmake)
include(${CHIBIOS}/os/kernel/kernel.cmake)
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
include(${CHIBIOS}/test/test.cmake)
include(external/external.cmake)
# Define linker script file here
set(LDSCRIPT ${PORTLD}/LPC43xx_M0.ld)
@@ -182,6 +183,7 @@ set(CPPSRC
io_convert.cpp
io_file.cpp
io_wave.cpp
iq_trim.cpp
irq_controls.cpp
irq_lcd_frame.cpp
irq_rtc.cpp
@@ -238,43 +240,63 @@ set(CPPSRC
ui/ui_textentry.cpp
ui/ui_tone_key.cpp
ui/ui_transmitter.cpp
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/analog_tv_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/gps_sim_app.cpp
apps/lge_app.cpp
apps/lge_app.cpp
apps/pocsag_app.cpp
# apps/replay_app.cpp
apps/soundboard_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_afsk_rx.cpp
apps/ui_aprs_rx.cpp
apps/ui_btle_rx.cpp
apps/ui_nrf_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_bht_tx.cpp
apps/ui_dfu_menu.cpp
apps/ui_btle_rx.cpp
apps/ui_coasterp.cpp
apps/ui_debug.cpp
apps/ui_dfu_menu.cpp
apps/ui_encoders.cpp
apps/ui_fileman.cpp
apps/ui_flash_utility.cpp
apps/ui_sd_over_usb.cpp
apps/ui_freqman.cpp
apps/ui_iq_trim.cpp
apps/ui_jammer.cpp
#apps/ui_keyfob.cpp
# apps/ui_keyfob.cpp
apps/ui_lcr.cpp
apps/lge_app.cpp
apps/ui_level.cpp
apps/ui_looking_glass_app.cpp
apps/ui_mictx.cpp
apps/ui_modemsetup.cpp
apps/ui_morse.cpp
apps/ui_nrf_rx.cpp
# apps/ui_nuoptix.cpp
apps/ui_playlist.cpp
apps/ui_pocsag_tx.cpp
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
apps/ui_remote.cpp
apps/ui_scanner.cpp
apps/ui_search.cpp
apps/ui_sd_over_usb.cpp
apps/ui_sd_wipe.cpp
apps/ui_search.cpp
apps/ui_settings.cpp
apps/ui_siggen.cpp
apps/ui_sonde.cpp
apps/ui_sstvtx.cpp
apps/ui_spectrum_painter_image.cpp
apps/ui_spectrum_painter_text.cpp
apps/ui_spectrum_painter.cpp
apps/ui_ss_viewer.cpp
apps/ui_sstvtx.cpp
# apps/ui_test.cpp
apps/ui_text_editor.cpp
apps/ui_tone_search.cpp
@@ -282,26 +304,6 @@ set(CPPSRC
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_whipcalc.cpp
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/analog_tv_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/lge_app.cpp
apps/pocsag_app.cpp
# apps/replay_app.cpp
apps/ui_playlist.cpp
apps/gps_sim_app.cpp
apps/soundboard_app.cpp
apps/ui_recon.cpp
apps/ui_recon_settings.cpp
apps/ui_level.cpp
apps/tpms_app.cpp
apps/tpms_app.cpp
apps/ui_spectrum_painter.cpp
apps/ui_spectrum_painter_image.cpp
apps/ui_spectrum_painter_text.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
@@ -320,6 +322,9 @@ set(CPPSRC
${CPLD_20150901_DATA_CPP}
${CPLD_20170522_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
${EXTCPPSRC}
)
# C sources to be compiled in ARM mode regardless of the global setting.
@@ -395,7 +400,7 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
# List all default C defines here, like -D_DEBUG=1
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"'")
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5}")
# List all default ASM defines here, like -D_DEBUG=1
set(DADEFS)
@@ -464,12 +469,13 @@ set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
add_definitions(${DEFS})
include_directories(. ${INCDIR})
link_directories(${LLIBDIR})
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
target_link_libraries(${PROJECT_NAME}.elf ${LIBS} "-L${CMAKE_CURRENT_LIST_DIR}/external")
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
add_custom_command(
OUTPUT ${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin --remove-section=.external_app_*
COMMAND ${EXPORT_EXTERNAL_APP_IMAGES} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_OBJCOPY} ${EXTAPPLIST}
DEPENDS ${PROJECT_NAME}.elf
)
+36 -39
View File
@@ -156,6 +156,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
File f;
auto path = get_settings_path(std::string{store_name});
make_new_directory(SETTINGS_DIR);
auto error = f.create(path);
if (error)
return false;
@@ -244,8 +245,7 @@ SettingsManager::SettingsManager(
: app_name_{app_name},
settings_{},
bindings_{},
loaded_{false},
radio_loaded_{false} {
loaded_{false} {
settings_.mode = mode;
settings_.options = options;
@@ -253,53 +253,50 @@ SettingsManager::SettingsManager(
additional_settings.reserve(17 + additional_settings.size());
bindings_ = std::move(additional_settings);
// Additional settings should always be loaded because apps now rely
// on being able to store UI settings, config, etc. The radio settings
// are only loaded if the global option has been enabled.
// Add the radio setting bindings if either load or save are enabled.
if (portapack::persistent_memory::load_app_settings() ||
portapack::persistent_memory::save_app_settings()) {
// Transmitter model settings.
if (flags_enabled(mode, Mode::TX)) {
bindings_.emplace_back("tx_frequency"sv, &settings_.tx_frequency);
bindings_.emplace_back("tx_amp"sv, &settings_.tx_amp);
bindings_.emplace_back("tx_gain"sv, &settings_.tx_gain);
bindings_.emplace_back("channel_bandwidth"sv, &settings_.channel_bandwidth);
}
// Settings should always be loaded because apps now rely
// on being able to store UI settings, config, etc.
// Receiver model settings.
if (flags_enabled(mode, Mode::RX)) {
bindings_.emplace_back("rx_frequency"sv, &settings_.rx_frequency);
bindings_.emplace_back("lna"sv, &settings_.lna);
bindings_.emplace_back("vga"sv, &settings_.vga);
bindings_.emplace_back("rx_amp"sv, &settings_.rx_amp);
bindings_.emplace_back("modulation"sv, &settings_.modulation);
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
// Common model settings.
bindings_.emplace_back("baseband_bandwidth"sv, &settings_.baseband_bandwidth);
bindings_.emplace_back("sampling_rate"sv, &settings_.sampling_rate);
bindings_.emplace_back("step"sv, &settings_.step);
bindings_.emplace_back("volume"sv, &settings_.volume);
// Transmitter model settings.
if (flags_enabled(mode, Mode::TX)) {
bindings_.emplace_back("tx_frequency"sv, &settings_.tx_frequency);
bindings_.emplace_back("tx_amp"sv, &settings_.tx_amp);
bindings_.emplace_back("tx_gain"sv, &settings_.tx_gain);
bindings_.emplace_back("channel_bandwidth"sv, &settings_.channel_bandwidth);
}
// Receiver model settings.
if (flags_enabled(mode, Mode::RX)) {
bindings_.emplace_back("rx_frequency"sv, &settings_.rx_frequency);
bindings_.emplace_back("lna"sv, &settings_.lna);
bindings_.emplace_back("vga"sv, &settings_.vga);
bindings_.emplace_back("rx_amp"sv, &settings_.rx_amp);
bindings_.emplace_back("modulation"sv, &settings_.modulation);
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
// Common model settings.
bindings_.emplace_back("baseband_bandwidth"sv, &settings_.baseband_bandwidth);
bindings_.emplace_back("sampling_rate"sv, &settings_.sampling_rate);
bindings_.emplace_back("step"sv, &settings_.step);
bindings_.emplace_back("volume"sv, &settings_.volume);
// RadioState should have initialized default radio parameters before this function;
// copy them to settings_ before checking settings .ini file (in case the file doesn't exist
// or doesn't include all parameters). Settings in the file can overwrite all, or a subset of parameters.
copy_from_radio_model(settings_);
loaded_ = load_settings(app_name_, bindings_);
// Only copy to the radio if load was successful.
if (loaded_ && portapack::persistent_memory::load_app_settings()) {
radio_loaded_ = true;
if (loaded_)
copy_to_radio_model(settings_);
}
}
SettingsManager::~SettingsManager() {
// Only save radio settings when the option is enabled.
if (portapack::persistent_memory::save_app_settings())
copy_from_radio_model(settings_);
copy_from_radio_model(settings_);
save_settings(app_name_, bindings_);
}
+3 -3
View File
@@ -36,6 +36,9 @@
#include "max283x.hpp"
#include "string_format.hpp"
// Folder to store app settings, pmem_fileflag, and date_fileflag
#define SETTINGS_DIR u"/SETTINGS"
// Bring in the string_view literal.
using std::literals::operator""sv;
@@ -170,8 +173,6 @@ class SettingsManager {
/* True if settings were successfully loaded from file. */
bool loaded() const { return loaded_; }
/* True if radio settings were successfully loaded from file. */
bool radio_loaded() const { return radio_loaded_; }
Mode mode() const { return settings_.mode; }
AppSettings& raw() { return settings_; }
@@ -181,7 +182,6 @@ class SettingsManager {
AppSettings settings_;
SettingBindings bindings_;
bool loaded_;
bool radio_loaded_;
};
} // namespace app_settings
+2 -1
View File
@@ -60,11 +60,12 @@ class ACARSAppView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{
131550000 /* frequency */,
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_acars.hpp", app_settings::Mode::RX};
"rx_acars", app_settings::Mode::RX};
bool logging{false};
uint32_t packet_counter{0};
-3
View File
@@ -382,9 +382,6 @@ AISAppView::AISAppView(NavigationView& nav)
recent_entry_detail_view.hidden(true);
if (!settings_.radio_loaded())
receiver_model.set_target_frequency(initial_target_frequency);
receiver_model.enable();
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
+1 -2
View File
@@ -162,9 +162,8 @@ class AISAppView : public View {
std::string title() const override { return "AIS RX"; };
private:
static constexpr uint32_t initial_target_frequency = 162025000;
RxRadioState radio_state_{
162025000 /* frequency*/,
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
+6 -1
View File
@@ -44,6 +44,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
&field_vga,
&option_bandwidth,
&option_format,
&check_trim,
&record_view,
&waterfall,
});
@@ -59,6 +60,10 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
record_view.set_file_type((RecordView::FileType)file_type);
};
check_trim.on_select = [this](Checkbox&, bool v) {
record_view.set_auto_trim(v);
};
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
@@ -92,7 +97,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
};
receiver_model.enable();
option_bandwidth.set_by_value(500000); // better by_value than by option_bandwidth.set_selected_index(4), Preselected default option 500kHz.
option_bandwidth.set_by_value(500000);
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
@@ -92,6 +92,12 @@ class CaptureAppView : public View {
{{"C16", RecordView::FileType::RawS16},
{"C8", RecordView::FileType::RawS8}}};
Checkbox check_trim{
{23 * 8, 1 * 16},
4,
"Trim",
/*small*/ true};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
u"BBD_????.*",
-3
View File
@@ -110,9 +110,6 @@ ERTAppView::ERTAppView(NavigationView&) {
&recent_entries_view,
});
if (!settings_.radio_loaded())
receiver_model.set_target_frequency(initial_target_frequency);
receiver_model.enable();
logger = std::make_unique<ERTLogger>();
+1 -2
View File
@@ -123,12 +123,11 @@ class ERTAppView : public View {
std::string title() const override { return "ERT RX"; };
private:
static constexpr uint32_t initial_target_frequency = 911600000;
ERTRecentEntries recent{};
std::unique_ptr<ERTLogger> logger{};
RxRadioState radio_state_{
911600000 /* frequency */,
2500000 /* bandwidth */,
4194304 /* sampling rate */};
app_settings::SettingsManager settings_{
+4 -6
View File
@@ -74,7 +74,10 @@ void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
auto duration = ms_duration(file_size, transmitter_model.sampling_rate(), 2);
text_duration.set(to_string_time_ms(duration));
button_play.focus();
// TODO: fix in UI framework with 'try_focus()'?
// Hack around focus getting called by ctor before parent is set.
if (parent())
button_play.focus();
}
void GpsSimAppView::on_tx_progress(const uint32_t progress) {
@@ -173,11 +176,6 @@ GpsSimAppView::GpsSimAppView(
&waterfall,
});
if (!settings_.radio_loaded()) {
field_frequency.set_value(initial_target_frequency);
transmitter_model.set_sampling_rate(2600000);
}
field_frequency.set_step(5000);
button_play.on_select = [this](ImageButton&) {
+6 -15
View File
@@ -24,9 +24,6 @@
#ifndef __GPS_SIM_APP_HPP__
#define __GPS_SIM_APP_HPP__
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_widget.hpp"
@@ -54,21 +51,17 @@ class GpsSimAppView : public View {
std::string title() const override { return "GPS Sim TX"; };
private:
static constexpr uint32_t initial_target_frequency = 1575420000;
NavigationView& nav_;
RxRadioState radio_state_{
3000000 /* bandwidth */,
500000 /* sampling rate */
TxRadioState radio_state_{
1575420000 /* frequency */,
15000000 /* bandwidth */,
2600000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_gps", app_settings::Mode::TX};
static constexpr ui::Dim header_height = 3 * 16;
int32_t tx_gain{47};
bool rf_amp{true}; // aux private var to store temporal, same as Replay App rf_amp user selection.
static constexpr uint32_t baseband_bandwidth = 3000000; // filter bandwidth
const size_t read_size{16384};
const size_t buffer_count{3};
@@ -109,10 +102,8 @@ class GpsSimAppView : public View {
nav_};
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
74, 1 * 8, SHORT_UI // x(columns), y (line) position. (Used in Replay / GPS Simul / Playlist App)
// 10*8, 2*8, NORMAL_UI // x(columns), y (line) position. (Used in Mic App)
};
{11 * 8, 2 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
+4
View File
@@ -238,6 +238,10 @@ void LGEView::start_tx() {
tx_view.set_dirty();
}
/* By experimental test, previous fw 1.7.4 seems to have setting , tx LPF = 5Mhz
This LGE - seems to be for controlling a "laser tag equipment" (Havoc app) , modulated FSK , low bauds,
and using max GUI fm dev 150k , we are still in NBFM, let's reduce slightly original TX LPF 5M ->2M5*/
transmitter_model.set_baseband_bandwidth(2'500'000);
transmitter_model.enable();
chThdSleep(100);
+1
View File
@@ -51,6 +51,7 @@ class LGEView : public View {
};
TxRadioState radio_state_{
868067000 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
+54 -26
View File
@@ -54,35 +54,32 @@ POCSAGSettingsView::POCSAGSettingsView(
POCSAGSettings& settings)
: settings_{settings} {
add_children(
{&check_beta,
{&labels,
&check_log,
&check_log_raw,
&check_small_font,
&check_hide_bad,
&check_hide_addr_only,
&check_ignore,
&field_ignore,
&opt_filter_mode,
&field_filter_address,
&button_save});
check_beta.set_value(settings_.use_new_proc);
check_log.set_value(settings_.enable_logging);
check_log_raw.set_value(settings_.enable_raw_log);
check_small_font.set_value(settings_.enable_small_font);
check_hide_bad.set_value(settings_.hide_bad_data);
check_hide_addr_only.set_value(settings_.hide_addr_only);
check_ignore.set_value(settings_.enable_ignore);
field_ignore.set_value(settings_.address_to_ignore);
opt_filter_mode.set_by_value(settings_.filter_mode);
field_filter_address.set_value(settings_.filter_address);
button_save.on_select = [this, &nav](Button&) {
settings_.use_new_proc = check_beta.value();
settings_.enable_logging = check_log.value();
settings_.enable_raw_log = check_log_raw.value();
settings_.enable_small_font = check_small_font.value();
settings_.hide_bad_data = check_hide_bad.value();
settings_.hide_addr_only = check_hide_addr_only.value();
settings_.enable_ignore = check_ignore.value();
settings_.address_to_ignore = field_ignore.to_integer();
settings_.filter_mode = opt_filter_mode.selected_index_value();
settings_.filter_address = field_filter_address.to_integer();
nav.pop();
};
@@ -90,10 +87,7 @@ POCSAGSettingsView::POCSAGSettingsView(
POCSAGAppView::POCSAGAppView(NavigationView& nav)
: nav_{nav} {
if (settings_.use_new_proc)
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
else
baseband::run_image(portapack::spi_flash::image_tag_pocsag);
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
add_children(
{&rssi,
@@ -109,17 +103,16 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
&widget_baud,
&widget_bits,
&widget_frames,
&button_ignore_last,
&button_filter_last,
&button_config,
&console});
// No app settings, use fallbacks from pmem.
if (!app_settings_.loaded()) {
settings_.address_to_ignore = pmem::pocsag_ignore_address();
settings_.enable_ignore = settings_.address_to_ignore > 0;
}
if (!app_settings_.radio_loaded()) {
field_frequency.set_value(initial_target_frequency);
settings_.filter_address = pmem::pocsag_ignore_address();
settings_.filter_mode = (settings_.filter_address == 0)
? FILTER_NONE
: FILTER_DROP;
}
logger.append(LOG_ROOT_DIR "/POCSAG.TXT");
@@ -129,9 +122,11 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
receiver_model.set_squelch_level(v);
};
button_ignore_last.on_select = [this](Button&) {
settings_.enable_ignore = true;
settings_.address_to_ignore = last_address;
button_filter_last.on_select = [this](Button&) {
if (settings_.filter_mode == FILTER_NONE)
settings_.filter_mode = FILTER_DROP;
settings_.filter_address = last_address;
refresh_ui();
};
button_config.on_select = [this](Button&) {
@@ -155,15 +150,48 @@ POCSAGAppView::~POCSAGAppView() {
baseband::shutdown();
// Save pmem settings.
pmem::set_pocsag_ignore_address(settings_.address_to_ignore);
pmem::set_pocsag_ignore_address(settings_.filter_address);
pmem::set_pocsag_last_address(last_address); // For POCSAG TX.
}
void POCSAGAppView::refresh_ui() {
// Set console font style.
console.set_style(
settings_.enable_small_font
? &Styles::white_small
: &Styles::white);
// Update filter button text.
std::string btn_text = "Filter Last";
switch (settings_.filter_mode) {
case FILTER_DROP:
btn_text = "Ignore Last";
break;
case FILTER_KEEP:
btn_text = "Keep Last";
break;
case FILTER_NONE:
default:
btn_text = "Filter Last";
break;
}
button_filter_last.set_text(btn_text);
}
bool POCSAGAppView::ignore_address(uint32_t address) const {
switch (settings_.filter_mode) {
case FILTER_DROP:
return address == settings_.filter_address;
case FILTER_KEEP:
return address != settings_.filter_address;
case FILTER_NONE:
default:
return false;
}
}
void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
@@ -176,8 +204,8 @@ void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefi
return;
}
// Ignored address.
if (ignore() && pocsag_state.address == settings_.address_to_ignore) {
// Ignore address? TODO: could filter earlier.
if (ignore_address(pocsag_state.address)) {
console.write("\n" STR_COLOR_CYAN + prefix + " Ignored: " + to_string_dec_uint(pocsag_state.address));
last_address = pocsag_state.address;
return;
+30 -22
View File
@@ -114,15 +114,20 @@ class FrameIndicator : public Widget {
bool has_sync_ = false;
};
enum POCSAGFilter : uint8_t {
FILTER_NONE,
FILTER_DROP,
FILTER_KEEP
};
struct POCSAGSettings {
bool enable_small_font = false;
bool enable_logging = false;
bool enable_raw_log = false;
bool enable_ignore = false;
bool hide_bad_data = false;
bool hide_addr_only = false;
uint32_t address_to_ignore = 0;
bool use_new_proc = false;
uint8_t filter_mode = false;
uint32_t filter_address = 0;
};
class POCSAGSettingsView : public View {
@@ -135,11 +140,10 @@ class POCSAGSettingsView : public View {
private:
POCSAGSettings& settings_;
Checkbox check_beta{
{0 * 8 + 2, 18 * 16 - 4},
6,
"Beta",
true /*small*/};
Labels labels{
{{2 * 8, 12 * 16}, "Filter Mode:", Color::light_grey()},
{{2 * 8, 13 * 16}, "Filter Addr:", Color::light_grey()},
};
Checkbox check_log{
{2 * 8, 2 * 16},
@@ -166,13 +170,15 @@ class POCSAGSettingsView : public View {
22,
"Hide Addr Only"};
Checkbox check_ignore{
{2 * 8, 12 * 16},
22,
"Enable Ignored Address"};
OptionsField opt_filter_mode{
{15 * 8, 12 * 16},
4,
{{"None", FILTER_NONE},
{"Drop", FILTER_DROP},
{"Keep", FILTER_KEEP}}};
SymField field_ignore{
{7 * 8, 13 * 16 + 8},
SymField field_filter_address{
{15 * 8, 13 * 16},
7,
SymField::Type::Dec,
true /*explicit_edit*/};
@@ -191,15 +197,17 @@ class POCSAGAppView : public View {
void focus() override;
private:
static constexpr uint32_t initial_target_frequency = 466'175'000;
bool logging() const { return settings_.enable_logging; };
bool logging_raw() const { return settings_.enable_raw_log; };
bool ignore() const { return settings_.enable_ignore; };
bool hide_bad_data() const { return settings_.hide_bad_data; };
bool hide_addr_only() const { return settings_.hide_addr_only; };
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState radio_state_{
466175000 /* frequency*/,
max283x::filter::bandwidth_minimum /* bandwidth */,
3072000 /* sampling rate */
};
// Settings
POCSAGSettings settings_{};
@@ -210,14 +218,14 @@ class POCSAGAppView : public View {
{"small_font"sv, &settings_.enable_small_font},
{"enable_logging"sv, &settings_.enable_logging},
{"enable_raw_log"sv, &settings_.enable_raw_log},
{"enable_ignore"sv, &settings_.enable_ignore},
{"address_to_ignore"sv, &settings_.address_to_ignore},
{"filter_mode"sv, &settings_.filter_mode},
{"filter_address"sv, &settings_.filter_address},
{"hide_bad_data"sv, &settings_.hide_bad_data},
{"hide_addr_only"sv, &settings_.hide_addr_only},
{"use_new_proc"sv, &settings_.use_new_proc},
}};
void refresh_ui();
bool ignore_address(uint32_t address) const;
void handle_decoded(Timestamp timestamp, const std::string& prefix);
void on_packet(const POCSAGPacketMessage* message);
void on_stats(const POCSAGStatsMessage* stats);
@@ -273,9 +281,9 @@ class POCSAGAppView : public View {
BaudIndicator widget_baud{
{8 * 9 + 1, 1 * 16 + 2}};
Button button_ignore_last{
Button button_filter_last{
{10 * 8, 1 * 16, 12 * 8, 20},
"Ignore Last"};
"Filter Last"};
Button button_config{
{22 * 8, 1 * 16, 8 * 8, 20},
-3
View File
@@ -203,9 +203,6 @@ ReplayAppView::ReplayAppView(
ReplayAppView::~ReplayAppView() {
transmitter_model.disable();
display.fill_rectangle({0, 0, 240, 320}, Color::black()); // Solving sometimes visible bottom waterfall artifacts, clearing all LCD pixels.
chThdSleepMilliseconds(40); // (that happened sometimes if we interrupt the waterfall play at the beggining of the play around 25% and exit )
baseband::shutdown();
}
+2 -8
View File
@@ -23,9 +23,6 @@
#ifndef __REPLAY_APP_HPP__
#define __REPLAY_APP_HPP__
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_widget.hpp"
@@ -99,16 +96,13 @@ class ReplayAppView : public View {
ProgressBar progressbar{
{18 * 8, 1 * 16, 12 * 8, 16}};
// TODO: Does this need to be a frequency field at all?
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
nav_};
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
74, 1 * 8, SHORT_UI // x(columns), y (line) position. (Uused in Repay / GPS Simul / Playlist App)
// 10*8, 2*8, NORMAL_UI // x(columns), y (line) position. (Used in Mic App)
};
{11 * 8, 2 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
@@ -50,6 +50,7 @@ class SoundBoardView : public View {
private:
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
-3
View File
@@ -157,9 +157,6 @@ TPMSAppView::TPMSAppView(NavigationView&) {
&field_vga,
&recent_entries_view});
if (!settings_.radio_loaded())
receiver_model.set_target_frequency(initial_target_frequency);
receiver_model.enable();
options_band.on_change = [this](size_t, OptionsField::value_t v) {
+2 -3
View File
@@ -102,9 +102,8 @@ class TPMSAppView : public View {
std::string title() const override { return "TPMS RX"; };
private:
static constexpr uint32_t initial_target_frequency = 315000000;
RxRadioState radio_state_{
315000000 /* frequency*/,
1750000 /* bandwidth */,
2457600 /* sampling rate */};
app_settings::SettingsManager settings_{
@@ -135,7 +134,7 @@ class TPMSAppView : public View {
3,
{
{"315", 315000000},
{"433", 433920000},
{"434", 433920000},
}};
OptionsField options_pressure{
+10 -1
View File
@@ -34,6 +34,8 @@ using namespace portapack;
namespace ui {
bool ac_details_view_active{false};
template <>
void RecentEntriesTable<AircraftRecentEntries>::draw(
const Entry& entry,
@@ -182,6 +184,7 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
break;
}
button_close.on_select = [&nav](Button&) {
ac_details_view_active = false;
nav.pop();
};
};
@@ -217,6 +220,7 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
}
ADSBRxDetailsView::~ADSBRxDetailsView() {
ac_details_view_active = false;
on_close_();
}
@@ -260,6 +264,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
button_aircraft_details.on_select = [this, &nav](Button&) {
ac_details_view_active = true;
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(entry_copy, [this]() { send_updates = false; });
send_updates = false;
};
@@ -433,6 +438,11 @@ void ADSBRxView::updateDetailsAndMap(int ageStep) {
ui::GeoMarker marker;
bool storeNewMarkers = false;
// NB: Temporarily pausing updates in rtc_timer_tick context when viewing AC Details screen (kludge for some Guru faults)
// TODO: More targeted blocking of updates in rtc_timer_tick when ADSB processes are running
if (ac_details_view_active)
return;
// Sort and truncate the entries, grouped, newest group first
sort_entries_by_state();
truncate_entries(recent);
@@ -511,7 +521,6 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
baseband::set_adsb();
receiver_model.set_target_frequency(1'090'000'000);
receiver_model.enable();
}
+1
View File
@@ -342,6 +342,7 @@ class ADSBRxView : public View {
private:
RxRadioState radio_state_{
1090000000 /* frequency */,
2500000 /* bandwidth */,
2000000 /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
+3
View File
@@ -296,6 +296,9 @@ void ADSBTxView::generate_frames() {
void ADSBTxView::start_tx() {
generate_frames();
/* Already tested , with SDR Angel + another Hackrf RX and dump1090 + SDR RLT. Final conclusion is TX LPF 6 Mhz is
* the best settings to fulfill ADSB transponder spectrum mask requirements (<=-20 dB's at +-7Mhz , <=-40 dB's at +-23Mhz )
* and not showing any ADSB data decoding degradation.*/
transmitter_model.enable();
baseband::set_adsb();
+2 -1
View File
@@ -203,7 +203,8 @@ class ADSBTxView : public View {
};*/
TxRadioState radio_state_{
10000000 /* bandwidth */,
1090000000 /* frequency */,
6000000 /* bandwidth */,
4000000 /* sampling rate */
};
app_settings::SettingsManager settings_{
+5 -1
View File
@@ -190,7 +190,11 @@ class APRSRxView : public View {
bool reset_console = false;
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState radio_state_{
144390000 /* frequency */,
1750000 /* bandwidth */,
3072000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_aprs", app_settings::Mode::RX};
+1
View File
@@ -46,6 +46,7 @@ class APRSTXView : public View {
private:
TxRadioState radio_state_{
144390000 /* frequency */,
1750000 /* bandwidth */,
AFSK_TX_SAMPLERATE /* sampling rate */
};
-1
View File
@@ -54,7 +54,6 @@ BTLERxView::BTLERxView(NavigationView& nav)
&console});
// Auto-configure modem for LCR RX (TODO: remove later)
field_frequency.set_value(2426000000);
auto def_bell202 = &modem_defs[0];
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
serial_format_t serial_format;
+1
View File
@@ -50,6 +50,7 @@ class BTLERxView : public View {
NavigationView& nav_;
RxRadioState radio_state_{
2426000000 /* frequency */,
4000000 /* bandwidth */,
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
+2 -1
View File
@@ -52,11 +52,12 @@ class CoasterPagerView : public View {
tx_modes tx_mode = IDLE;
TxRadioState radio_state_{
433920000, /* frequency */
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_coaster", app_settings::Mode::TX};
"tx_burger", app_settings::Mode::TX};
void start_tx();
void generate_frame();
+13 -43
View File
@@ -34,6 +34,7 @@
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "ui_painter.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
@@ -398,18 +399,22 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({
{"Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
//{ "Radio State", ui::Color::white(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { stack_dump(); }},
//{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }}, // temporarily disabled to conserve ROM space
{"Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
//{ "Radio State", ui::Color::white(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav)) {
add_item(gridItem);
};
set_max_rows(2); // allow wider buttons
}
@@ -469,41 +474,6 @@ uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
}
/* DebugFontsView *******************************************************/
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
auto char_width{font_style->font.char_width()};
auto char_height{font_style->font.line_height()};
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
uint16_t line_pos{y_offset};
painter.draw_string({0, y_offset}, *font_style, font_name);
// Displaying ASCII+extended characters from 0x20 to 0xFF
for (uint8_t c = 0; c <= 0xDF; c++) {
line_pos = y_offset + ((c / cpl) + 2) * char_height;
if ((c % cpl) == 0)
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
}
return line_pos + char_height;
}
void DebugFontsView::paint(Painter& painter) {
int16_t line_pos;
line_pos = display_font(painter, 32, &Styles::white, "Fixed 8x16");
display_font(painter, line_pos + 16, &Styles::white_small, "Fixed 5x8");
}
DebugFontsView::DebugFontsView(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
}
/* DebugScreenTest ****************************************************/
DebugScreenTest::DebugScreenTest(NavigationView& nav)
-11
View File
@@ -310,17 +310,6 @@ class DebugPmemView : public View {
uint32_t registers_widget_feed(const size_t register_number);
};
class DebugFontsView : public View {
public:
DebugFontsView(NavigationView& nav);
void paint(Painter& painter) override;
std::string title() const override { return "Fonts"; };
private:
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
NavigationView& nav_;
};
class DebugScreenTest : public View {
public:
DebugScreenTest(NavigationView& nav);
+7 -3
View File
@@ -92,7 +92,9 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
&text_info_line_5,
&text_info_line_6,
&text_info_line_7,
&text_info_line_8});
&text_info_line_8,
&text_info_line_9,
&text_info_line_10});
}
void DfuMenu2::paint(Painter& painter) {
@@ -103,10 +105,12 @@ void DfuMenu2::paint(Painter& painter) {
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
text_info_line_8.set("");
text_info_line_8.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
constexpr auto margin = 5;
constexpr auto lines = 8 + 2;
constexpr auto lines = 10 + 2;
painter.fill_rectangle(
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
+9 -5
View File
@@ -77,17 +77,19 @@ class DfuMenu2 : public View {
private:
NavigationView& nav_;
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 8 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Receiver"};
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 14 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Radio Settings"};
Labels labels{
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "Tgt freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "Bandwidt:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "Sampl Rt:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "", Color::dark_cyan()}};
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()}};
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
@@ -97,6 +99,8 @@ class DfuMenu2 : public View {
Text text_info_line_6{{14 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_7{{14 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
} /* namespace ui */
@@ -269,6 +269,9 @@ void EncodersView::start_tx(const bool scan) {
repeat_index = 1;
update_progress();
/* Setting TX LPF 1M75 in this TX OOK App , We got same results as fw 1.7.4
* Looking max BW of this app, we tested , Selecting OOK type 145026 with CLK 455K and max DEV. 150k,
* and we got BW +-2Mhz , with that TX LPF 1M75, it is fine.*/
transmitter_model.enable();
baseband::set_ook_data(
@@ -185,6 +185,7 @@ class EncodersView : public View {
};
TxRadioState radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
+30 -12
View File
@@ -27,8 +27,10 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_playlist.hpp"
#include "ui_remote.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp"
#include "ui_iq_trim.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
@@ -44,6 +46,7 @@ static const fs::path c16_ext{u".C16"};
static const fs::path cxx_ext{u".C*"};
static const fs::path png_ext{u".PNG"};
static const fs::path bmp_ext{u".BMP"};
static const fs::path rem_ext{u".REM"};
} // namespace ui
namespace {
@@ -106,14 +109,17 @@ bool partner_file_prompt(
if (partner.empty())
return false;
nav.push_under_current<ModalMessageView>(
"Partner File",
partner.filename().string() + "\n" + action_name + " this file too?",
YESNO,
[&nav, partner, on_partner_action](bool choice) {
if (on_partner_action)
on_partner_action(partner, choice);
});
// Display the continuation UI once the current has been popped.
nav.set_on_pop([&nav, partner, action_name, on_partner_action] {
nav.display_modal(
"Partner File",
partner.filename().string() + "\n" + action_name + " this file too?",
YESNO,
[&nav, partner, on_partner_action](bool choice) {
if (on_partner_action)
on_partner_action(partner, choice);
});
});
return true;
}
@@ -187,8 +193,8 @@ FileManBaseView::FileManBaseView(
&text_current,
&button_exit});
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
button_exit.on_select = [this](Button&) {
nav_.pop();
};
if (!sdcIsCardInserted(&SDCD1)) {
@@ -323,9 +329,9 @@ FileLoadView::FileLoadView(
if (get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else {
nav_.pop();
if (on_changed)
on_changed(get_selected_full_path());
nav_.pop();
}
};
}
@@ -525,6 +531,9 @@ bool FileManagerView::handle_file_open() {
nav_.push<SplashViewer>(path);
reload_current();
return true;
} else if (path_iequal(rem_ext, ext)) {
nav_.push<RemoteView>(path);
return true;
}
return false;
@@ -558,6 +567,7 @@ FileManagerView::FileManagerView(
&button_new_file,
&button_open_notepad,
&button_rename_timestamp,
&button_open_iq_trim,
});
menu_view.on_highlight = [this]() {
@@ -629,7 +639,7 @@ FileManagerView::FileManagerView(
button_open_notepad.on_select = [this]() {
if (selected_is_valid() && !get_selected_entry().is_directory) {
auto path = get_selected_full_path();
nav_.replace<TextEditorView>(path);
nav_.push<TextEditorView>(path);
} else
nav_.display_modal("Open in Notepad", "Can't open that in Notepad.");
};
@@ -640,6 +650,14 @@ FileManagerView::FileManagerView(
} else
nav_.display_modal("Timestamp Rename", "Can't rename that.");
};
button_open_iq_trim.on_select = [this]() {
auto path = get_selected_full_path();
if (selected_is_valid() && !get_selected_entry().is_directory && is_cxx_capture_file(path)) {
nav_.push<IQTrimView>(path);
} else
nav_.display_modal("IQ Trim", "Not a capture file.");
};
}
} // namespace ui
+9 -1
View File
@@ -77,7 +77,8 @@ class FileManBaseView : public View {
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // PPL is the file extension for playlist app
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
{u".REM", &bitmap_icon_remote, ui::Color::orange()}, // Remote
{u"", &bitmap_icon_file, ui::Color::light_grey()} // NB: Must be last.
};
@@ -269,6 +270,13 @@ class FileManagerView : public FileManBaseView {
{4 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_options_datetime,
Color::orange(),
/*vcenter*/ true};
NewButton button_open_iq_trim{
{9 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_trim,
Color::orange()};
};
+22 -74
View File
@@ -23,6 +23,7 @@
#include "ui_freqman.hpp"
#include "binder.hpp"
#include "event_m0.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
@@ -92,9 +93,9 @@ void FreqManBaseView::focus() {
// TODO: Shouldn't be on focus.
if (error_ == ERROR_ACCESS) {
nav_.display_modal("Error", "File access error", ABORT, nullptr);
nav_.display_modal("Error", "File access error", ABORT);
} else if (error_ == ERROR_NOFILES) {
nav_.display_modal("Error", "No database files\nin /FREQMAN", ABORT, nullptr);
nav_.display_modal("Error", "No database files\nin /FREQMAN", ABORT);
} else {
options_category.focus();
}
@@ -152,28 +153,14 @@ FrequencySaveView::FrequencySaveView(
add_children(
{&labels,
&big_display,
&button_clear,
&button_edit,
&button_save,
&text_description});
&field_description});
entry_.type = freqman_type::Single;
entry_.frequency_a = value;
entry_.description = to_string_timestamp(rtc_time::now());
refresh_ui();
button_clear.on_select = [this, &nav](Button&) {
entry_.description = "";
refresh_ui();
};
button_edit.on_select = [this, &nav](Button&) {
temp_buffer_ = entry_.description;
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
entry_.description = new_desc;
refresh_ui();
});
};
bind(field_description, entry_.description, nav);
button_save.on_select = [this, &nav](Button&) {
db_.insert_entry(db_.entry_count(), entry_);
@@ -181,9 +168,13 @@ FrequencySaveView::FrequencySaveView(
};
}
void FrequencySaveView::focus() {
refresh_ui();
FreqManBaseView::focus();
}
void FrequencySaveView::refresh_ui() {
big_display.set(entry_.frequency_a);
text_description.set(entry_.description);
}
/* FrequencyLoadView *************************************/
@@ -376,69 +367,26 @@ FrequencyEditView::FrequencyEditView(
field_step.options().insert(
field_step.options().begin(), {"None", -1});
field_type.set_by_value((int32_t)entry_.type);
field_type.on_change = [this](size_t, auto value) {
entry_.type = static_cast<freqman_type>(value);
bind(field_type, entry_.type, [this](auto) {
refresh_ui();
};
});
// TODO: this pattern should be able to be wrapped up.
field_freq_a.set_value(entry_.frequency_a);
field_freq_a.on_change = [this](rf::Frequency f) {
entry_.frequency_a = f;
bind(field_freq_a, entry_.frequency_a, nav, [this](auto) {
refresh_ui();
};
field_freq_a.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_a.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_a.set_value(f);
};
};
});
field_freq_b.set_value(entry_.frequency_b);
field_freq_b.on_change = [this](rf::Frequency f) {
entry_.frequency_b = f;
bind(field_freq_b, entry_.frequency_b, nav, [this](auto) {
refresh_ui();
};
field_freq_b.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_b.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_freq_b.set_value(f);
};
};
});
field_modulation.set_by_value((int32_t)entry_.modulation);
field_modulation.on_change = [this](size_t, auto value) {
entry_.modulation = static_cast<freqman_index_t>(value);
bind(field_modulation, entry_.modulation, [this](auto) {
populate_bandwidth_options();
};
});
field_bandwidth.set_by_value((int32_t)entry_.bandwidth);
field_bandwidth.on_change = [this](size_t, auto value) {
entry_.bandwidth = static_cast<freqman_index_t>(value);
};
field_step.set_by_value((int32_t)entry_.step);
field_step.on_change = [this](size_t, auto value) {
entry_.step = static_cast<freqman_index_t>(value);
};
field_tone.set_by_value((int32_t)entry_.tone);
field_tone.on_change = [this](size_t, auto value) {
entry_.tone = static_cast<freqman_index_t>(value);
};
field_description.set_text(entry_.description);
field_description.on_change = [this](TextField& tf) {
entry_.description = tf.get_text();
};
field_description.on_select = [this](TextField& tf) {
temp_buffer_ = tf.get_text();
text_prompt(nav_, temp_buffer_, FreqManBaseView::desc_edit_max,
[this, &tf](std::string& new_desc) {
tf.set_text(new_desc);
});
};
bind(field_bandwidth, entry_.bandwidth);
bind(field_step, entry_.step);
bind(field_tone, entry_.tone);
bind(field_description, entry_.description, nav_);
button_save.on_select = [this](Button&) {
if (on_save)
+4 -10
View File
@@ -87,9 +87,9 @@ class FrequencySaveView : public FreqManBaseView {
public:
FrequencySaveView(NavigationView& nav, const rf::Frequency value);
std::string title() const override { return "Save freq"; }
void focus() override;
private:
std::string temp_buffer_{};
freqman_entry entry_{};
void refresh_ui();
@@ -101,15 +101,9 @@ class FrequencySaveView : public FreqManBaseView {
Labels labels{
{{0 * 8, 6 * 16}, "Description:", Color::white()}};
Text text_description{{0 * 8, 7 * 16, 30 * 8, 1 * 16}};
Button button_clear{
{4 * 8, 10 * 16, 10 * 8, 2 * 16},
"Clear"};
Button button_edit{
{16 * 8, 10 * 16, 10 * 8, 2 * 16},
"Edit"};
TextField field_description{
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
""};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
+220
View File
@@ -0,0 +1,220 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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_iq_trim.hpp"
#include "complex.hpp"
#include "portapack.hpp"
#include "ui_fileman.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
IQTrimView::IQTrimView(NavigationView& nav)
: nav_{nav} {
add_children({
&labels,
&field_path,
&field_start,
&field_end,
&text_samples,
&text_max,
&field_cutoff,
&field_amplify,
&button_trim,
});
field_path.on_select = [this](TextField&) {
auto open_view = nav_.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
open_file(path);
};
};
text_samples.set_style(&Styles::light_grey);
text_max.set_style(&Styles::light_grey);
field_start.on_change = [this](int32_t v) {
if (field_end.value() < v)
field_end.set_value(v, false);
set_dirty();
};
field_end.on_change = [this](int32_t v) {
if (field_start.value() > v)
field_start.set_value(v, false);
set_dirty();
};
field_cutoff.set_value(7); // 7% of max is a good default.
field_cutoff.on_change = [this](int32_t) {
compute_range();
refresh_ui();
};
field_amplify.set_value(1); // 1X is default (no amplification)
field_amplify.on_change = [this](int32_t) {
refresh_ui();
};
button_trim.on_select = [this](Button&) {
if (trim_capture()) {
profile_capture();
compute_range();
refresh_ui();
}
};
}
IQTrimView::IQTrimView(NavigationView& nav, const fs::path& path)
: IQTrimView(nav) {
open_file(path);
}
void IQTrimView::open_file(const std::filesystem::path& path) {
path_ = std::move(path);
profile_capture();
compute_range();
refresh_ui();
}
void IQTrimView::paint(Painter& painter) {
if (info_) {
uint32_t power_cutoff = field_cutoff.value() * static_cast<uint64_t>(info_->max_power) / 100;
// Draw power buckets.
for (size_t i = 0; i < power_buckets_.size(); ++i) {
auto power = power_buckets_[i].power;
uint8_t amp = 0;
if (power > power_cutoff && info_->max_power > 0)
amp = (255ULL * power) / info_->max_power;
painter.draw_vline(
pos_lines + Point{(int)i, 0},
height_lines,
Color(amp, amp, amp));
}
// Draw trim range edges.
int start_x = screen_width * field_start.value() / info_->sample_count;
int end_x = screen_width * field_end.value() / info_->sample_count;
painter.draw_vline(
pos_lines + Point{start_x, 0},
height_lines,
Color::dark_green());
painter.draw_vline(
pos_lines + Point{end_x, 0},
height_lines,
Color::dark_red());
}
}
void IQTrimView::focus() {
field_path.focus();
}
void IQTrimView::refresh_ui() {
field_path.set_text(path_.filename().string());
text_samples.set(to_string_dec_uint(info_->sample_count));
// show max power after amplification applied
uint64_t power_amp = field_amplify.value() * field_amplify.value() * field_amplify.value() * field_amplify.value();
text_max.set(to_string_dec_uint(info_->max_power * power_amp));
// show max power in red if amplification is too high, causing clipping
uint32_t clipping_limit = (fs::capture_file_sample_size(path_) == sizeof(complex8_t)) ? 0x80 : 0x8000;
if ((field_amplify.value() * info_->max_iq) > clipping_limit)
text_max.set_style(&Styles::red);
else
text_max.set_style(&Styles::light_grey);
set_dirty();
}
void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
auto max_range = info_ ? info_->sample_count : 0;
auto step = info_ ? info_->sample_count / screen_width : 0;
field_start.set_range(0, max_range);
field_start.set_step(step);
field_end.set_range(0, max_range);
field_end.set_step(step);
field_start.set_value(trim_range.start_sample, false);
field_end.set_value(trim_range.end_sample, false);
}
void IQTrimView::profile_capture() {
power_buckets_ = {};
iq::PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
progress_ui.show_reading();
info_ = iq::profile_capture(path_, buckets);
progress_ui.clear();
}
void IQTrimView::compute_range() {
if (!info_)
return;
iq::PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
auto trim_range = iq::compute_trim_range(*info_, buckets, field_cutoff.value());
update_range_controls(trim_range);
}
bool IQTrimView::trim_capture() {
if (!info_) {
nav_.display_modal("Error", "Open a file first.");
return false;
}
bool trimmed = false;
iq::TrimRange trim_range{
static_cast<uint32_t>(field_start.value()),
static_cast<uint32_t>(field_end.value()),
info_->sample_size};
if (trim_range.start_sample >= trim_range.end_sample) {
nav_.display_modal("Error", "Invalid trimming range.");
return false;
}
progress_ui.show_trimming();
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback(), field_amplify.value());
progress_ui.clear();
if (!trimmed)
nav_.display_modal("Error", "Trimming failed.");
return trimmed;
}
} // namespace ui
+164
View File
@@ -0,0 +1,164 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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_IQ_TRIM_H__
#define __UI_IQ_TRIM_H__
#include "file.hpp"
#include "iq_trim.hpp"
#include "optional.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include <array>
namespace ui {
/* Helper for drawing progress related to IQ trimming. */
/* TODO: Ideally this would all be done on second thread. */
class TrimProgressUI {
public:
void show_reading() {
clear();
p.draw_string({6 * 8, 5 * 16}, Styles::yellow, "Reading Capture...");
}
void show_trimming() {
clear();
p.draw_string({5 * 8, 5 * 16}, Styles::yellow, "Trimming Capture...");
}
void show_progress(uint8_t percent) {
auto width = percent * screen_width / 100;
p.draw_hline({0, 6 * 16 + 2}, width, Color::yellow());
}
void clear() {
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Color::black());
}
auto get_callback() {
return [this](uint8_t percent) { show_progress(percent); };
}
private:
Painter p{};
};
class IQTrimView : public View {
public:
IQTrimView(NavigationView& nav);
IQTrimView(NavigationView& nav, const std::filesystem::path& path);
std::string title() const override { return "IQ Trim"; }
void paint(Painter& painter) override;
void focus() override;
private:
void open_file(const std::filesystem::path& path);
/* Update controls with latest values. */
void refresh_ui();
/* Update the start/end controls with trim range info. */
void update_range_controls(iq::TrimRange trim_range);
/* Collect capture info and samples to draw the UI. */
void profile_capture();
/* Determine the start and end buckets based on the cutoff. */
void compute_range();
/* Trims the capture file based on the settings. */
bool trim_capture();
NavigationView& nav_;
std::filesystem::path path_{};
Optional<iq::CaptureInfo> info_{};
std::array<iq::PowerBuckets::Bucket, screen_width> power_buckets_{};
TrimProgressUI progress_ui{};
Labels labels{
{{0 * 8, 0 * 16}, "Capture File:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Start :", Color::light_grey()},
{{0 * 8, 7 * 16}, "End :", Color::light_grey()},
{{0 * 8, 8 * 16}, "Samples:", Color::light_grey()},
{{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()},
{{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()},
{{12 * 8, 10 * 16}, "%", Color::light_grey()},
{{0 * 8, 12 * 16}, "Amplify:", Color::light_grey()},
{{10 * 8, 12 * 16}, "x", Color::light_grey()},
};
TextField field_path{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
Dim height_lines{2 * 16};
NumberField field_start{
{9 * 8, 6 * 16},
10,
{0, 0},
1,
' '};
NumberField field_end{
{9 * 8, 7 * 16},
10,
{0, 0},
1,
' '};
Text text_samples{
{9 * 8, 8 * 16, 10 * 8, 1 * 16},
"0"};
Text text_max{
{9 * 8, 9 * 16, 20 * 8, 1 * 16},
"0"};
NumberField field_cutoff{
{9 * 8, 10 * 16},
3,
{1, 100},
1,
' '};
NumberField field_amplify{
{9 * 8, 12 * 16},
1,
{1, 9},
1,
' '};
Button button_trim{
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
"Trim"};
};
} /* namespace ui */
#endif /*__UI_IQ_TRIM_H__*/
+3 -1
View File
@@ -261,6 +261,7 @@ void JammerView::start_tx() {
transmitter_model.set_rf_amp(field_amp.value());
transmitter_model.set_tx_gain(field_gain.value());
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
transmitter_model.enable();
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
@@ -288,7 +289,8 @@ void JammerView::on_timer() {
mscounter = 0;
if (jamming) {
if (cooling) {
if (++seconds >= field_timepause.value()) { // Re-start TX
if (++seconds >= field_timepause.value()) { // Re-start TX
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
transmitter_model.enable();
button_transmit.set_text("STOP");
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
+1
View File
@@ -101,6 +101,7 @@ class JammerView : public View {
private:
NavigationView& nav_;
TxRadioState radio_state_{
0 /* frequency */,
3500000 /* bandwidth */,
3072000 /* sampling rate */
};
-2
View File
@@ -222,8 +222,6 @@ KeyfobView::KeyfobView(
options_make.set_selected_index(0);
transmitter_model.set_target_frequency(433920000); // Fixed 433.92MHz
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
+1
View File
@@ -44,6 +44,7 @@ class KeyfobView : public View {
NavigationView& nav_;
TxRadioState radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
+2
View File
@@ -127,6 +127,8 @@ void LCRView::start_tx(const bool scan) {
modems::generate_data(lcr::generate_message(rgsb, litterals_list, options_ec.selected_index()), lcr_message_data);
/* It is AFSK modulation , measuring original fw 1.7.4 spectrum BW is just around 30khz , NBFM */
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF 1M75, same spectrum as previous fw 1.7.4
transmitter_model.enable();
memcpy(shared_memory.bb_data.data, lcr_message_data, sizeof(lcr_message_data));
+1
View File
@@ -82,6 +82,7 @@ class LCRView : public View {
};
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
AFSK_TX_SAMPLERATE /* sampling rate */
};
+39 -12
View File
@@ -87,6 +87,10 @@ void MicTXView::set_tx(bool enable) {
transmitting = true;
configure_baseband();
transmitter_model.set_target_frequency(tx_frequency); // Now, no need: transmitter_model.set_tx_gain(tx_gain), nor (rf_amp);
/* The max. Power Spectrum Densitiy in WFM with High tone mod level (80%) and high 32kHZ subtone as fmod. with max fdeviation 150k ,
BW aprox = 2 *(150K + 32K) = 364khz, then we just select the minimum TX LPF 1M75. */
transmitter_model.set_baseband_bandwidth(1'750'000);
transmitter_model.enable();
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
} else {
@@ -97,8 +101,11 @@ void MicTXView::set_tx(bool enable) {
transmitting = false;
configure_baseband();
transmitter_model.disable();
if (rx_enabled) // If audio RX is enabled and we've been transmitting
rxaudio(true); // Turn back on audio RX
if (rx_enabled) { // If audio RX is enabled and we've been transmitting
rxaudio(true); // Turn back on audio RX
vumeter.set_value(0); // Reset vumeter
vumeter.dirty(); // Force to refresh vumeter.
}
}
}
}
@@ -179,7 +186,7 @@ void MicTXView::rxaudio(bool is_on) {
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
audio::output::stop();
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
portapack::pin_i2s0_rx_sda.mode(3);
configure_baseband();
}
@@ -211,6 +218,7 @@ MicTXView::MicTXView(
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_mic_to_HP, // check box to activate "hear mic"
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
@@ -238,6 +246,7 @@ MicTXView::MicTXView(
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_mic_to_HP, // check box to activate "hear mic"
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
@@ -281,17 +290,17 @@ MicTXView::MicTXView(
shift_bits_s16 = 6; // -08 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
}
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off,and the check box "Hear Mic"
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
};
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's.
} else {
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod, shift audio data for AK4951, using top 8 bits s16 data (>>8)
options_ak4951_alc_mode.on_change = [this](size_t, int8_t v) {
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options, and the check box "Hear Mic"
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
};
}
@@ -471,6 +480,20 @@ MicTXView::MicTXView(
rogerbeep_enabled = v;
};
check_mic_to_HP.on_select = [this](Checkbox&, bool v) {
mic_to_HP_enabled = v;
if (mic_to_HP_enabled)
audio::input::loopback_mic_to_hp_enable();
else
audio::input::loopback_mic_to_hp_disable();
if (mic_to_HP_enabled) { // When user click to "hear mic to HP", we will select the higher acoustic sound Option MODE ALC or BOOST-
if (audio::debug::codec_name() == "WM8731") {
options_wm8731_boost_mode.set_selected_index(0); // In WM we always go to Boost +12 dBs respect reference level
} else if (ak4951_alc_and_wm8731_boost_GUI == 0) // In AK we are changing only that ALC index =0, because in that option, there is no sound.
options_ak4951_alc_mode.set_selected_index(1); // alc_mode =0 , means no ALC,no DIGITAL filter block (by passed), and that mode has no loopback mode.
}
};
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
bool_same_F_tx_rx_enabled = v;
field_rxfrequency.hidden(v); // Hide or show separated freq RX field. (When no hide user can enter down indep. freq for RX)
@@ -497,8 +520,12 @@ MicTXView::MicTXView(
check_rxactive.on_select = [this](Checkbox&, bool v) {
// vumeter.set_value(0); //Start with a clean vumeter
rx_enabled = v;
check_mic_to_HP.hidden(rx_enabled); // Togle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
if ((rx_enabled) && (transmitting))
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
// check_va.hidden(v); //Hide or show voice activation
rxaudio(v); // Activate-Deactivate audio rx accordingly
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
set_dirty(); // Refresh interface
};
@@ -598,7 +625,7 @@ MicTXView::MicTXView(
set_tx(false);
audio::set_rate(audio::Rate::Hz_24000);
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
}
MicTXView::MicTXView(
@@ -633,7 +660,7 @@ MicTXView::MicTXView(
MicTXView::~MicTXView() {
audio::input::stop();
transmitter_model.set_target_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
transmitter_model.set_target_frequency(tx_frequency);
transmitter_model.disable();
if (rx_enabled) // Also turn off audio rx if enabled
rxaudio(false);
+10 -7
View File
@@ -25,9 +25,6 @@
#ifndef __UI_MICTX_H__
#define __UI_MICTX_H__
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui.hpp"
@@ -85,6 +82,7 @@ class MicTXView : public View {
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
sampling_rate /* sampling rate */
};
@@ -96,6 +94,7 @@ class MicTXView : public View {
bool va_enabled{false};
bool ptt_enabled{true};
bool rogerbeep_enabled{false};
bool mic_to_HP_enabled{false};
bool bool_same_F_tx_rx_enabled{false};
bool rx_enabled{false};
uint32_t tone_key_index{};
@@ -219,10 +218,8 @@ class MicTXView : public View {
' '};
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
// 3*8, 2*8, SHORT_UI // x(columns), y (line) position. (used in Replay / GPS Simul / Playlist App's)
3 * 8, 2 * 8, NORMAL_UI // x(columns), y (line) position. (used in Mic App)
};
{3 * 8, 5 * 8},
/*short_ui*/ false};
OptionsField options_mode{
{24 * 8, 5 * 8},
@@ -281,6 +278,12 @@ class MicTXView : public View {
"Roger beep",
false};
Checkbox check_mic_to_HP{
{18 * 8, (14 * 8) + 4},
10,
"Hear Mic",
false};
Checkbox check_rxactive{
{3 * 8, (21 * 8) - 7},
8, // it was 18, but if it is string size should be 8
+2
View File
@@ -117,6 +117,8 @@ bool MorseView::start_tx() {
}
progressbar.set_max(symbol_count);
// By experimental spectrum measurment, we left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min).
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF .already tested in FM morse max tone 9,999k , max dev 150khz
transmitter_model.enable();
if (modulation == CW) {
+1
View File
@@ -70,6 +70,7 @@ class MorseView : public View {
uint32_t time_units{0};
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
-1
View File
@@ -54,7 +54,6 @@ NRFRxView::NRFRxView(NavigationView& nav)
&console});
// Auto-configure modem for LCR RX (will be removed later)
field_frequency.set_value(2480000000);
auto def_bell202 = &modem_defs[0];
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
serial_format_t serial_format;
+1
View File
@@ -50,6 +50,7 @@ class NRFRxView : public View {
NavigationView& nav_;
RxRadioState radio_state_{
2480000000 /* frequency */,
4000000 /* bandwidth */,
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
+1 -1
View File
@@ -39,7 +39,7 @@ namespace ui {
void NumbersStationView::focus() {
if (file_error)
nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT, nullptr);
nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT);
else
button_exit.focus();
}
+1
View File
@@ -56,6 +56,7 @@ class NumbersStationView : public View {
NavigationView& nav_;
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
+1
View File
@@ -56,6 +56,7 @@ class NuoptixView : public View {
};
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
+2 -1
View File
@@ -273,7 +273,7 @@ void PlaylistView::send_current_track() {
// ReplayThread starts immediately on construction; must be set before creating.
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
transmitter_model.set_sampling_rate(get_actual_sample_rate(current()->metadata.sample_rate));
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.set_baseband_bandwidth(current()->metadata.sample_rate <= 500'000 ? 1'750'000 : 2'500'000); // TX LPF min 1M75 for SR <=500K, and 2M5 (by experimental test) for SR >500K
transmitter_model.enable();
// Reset the transmit progress bar.
@@ -386,6 +386,7 @@ PlaylistView::PlaylistView(
&waterfall,
});
ensure_directory(u"PLAYLIST");
waterfall.show_audio_spectrum_view(false);
field_frequency.set_value(100'000'000);
+4 -10
View File
@@ -21,9 +21,6 @@
* Boston, MA 02110-1301, USA.
*/
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "bitmap.hpp"
#include "file.hpp"
@@ -63,7 +60,6 @@ class PlaylistView : public View {
// More header == less spectrum view.
static constexpr ui::Dim header_height = 6 * 16;
static constexpr uint32_t baseband_bandwidth = 2500000;
struct playlist_entry {
std::filesystem::path path{};
@@ -118,14 +114,11 @@ class PlaylistView : public View {
Text text_sample_rate{
{10 * 8, 1 * 16, 7 * 8, 16}};
/*v making there's 1px line (instead of two) between two progress bars,
* by letting 1px overlapped.
* So, since they overlapped 1px, they are visually same, and looks better.
*/
ProgressBar progressbar_track{
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
// (-1) to overlap with progressbar_track so there's
// only 1 pixel between them instead of 2.
ProgressBar progressbar_transmit{
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
@@ -135,7 +128,8 @@ class PlaylistView : public View {
// TODO: delay duration field.
TransmitterView2 tx_view{
9 * 8, 1 * 8, SHORT_UI};
{11 * 8, 2 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
@@ -81,6 +81,8 @@ bool POCSAGTXView::start_tx() {
transmitter_model.set_rf_amp(true);
transmitter_model.set_tx_gain(40);
// We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF . Pocsag is NBFM , using BW channel 25khz or 12khz
transmitter_model.enable();
uint8_t* data_ptr = shared_memory.bb_data.data;
@@ -60,6 +60,7 @@ class POCSAGTXView : public View {
NavigationView& nav_;
TxRadioState radio_state_{
466175000 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
+1
View File
@@ -142,6 +142,7 @@ class RDSView : public View {
RDS_flags rds_flags{};
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
+32 -45
View File
@@ -103,7 +103,6 @@ void ReconView::reset_indexes() {
void ReconView::update_description() {
if (frequency_list.empty() || current_entry().description.empty()) {
description = "...no description...";
return;
} else {
switch (current_entry().type) {
case freqman_type::Range:
@@ -117,7 +116,6 @@ void ReconView::update_description() {
}
description += current_entry().description;
}
desc_cycle.set(description);
}
@@ -459,8 +457,7 @@ ReconView::ReconView(NavigationView& nav)
rssi.set_focusable(true);
rssi.set_peak(true, 500);
rssi.on_select = [this](RSSI&) {
nav_.pop();
nav_.push<LevelView>();
nav_.replace<LevelView>();
};
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
@@ -497,6 +494,7 @@ ReconView::ReconView(NavigationView& nav)
button_manual_recon.on_select = [this](Button&) {
button_remove.set_text("DELETE");
button_add.hidden(false);
scanner_mode = false;
manual_mode = true;
recon_pause();
@@ -540,7 +538,7 @@ ReconView::ReconView(NavigationView& nav)
button_scanner_mode.set_style(&Styles::white);
button_scanner_mode.set_text("MANUAL");
file_name.set_style(&Styles::white);
file_name.set("MANUAL RANGE RECON");
file_name.set("MANUAL => " + output_file);
desc_cycle.set_style(&Styles::white);
last_entry.modulation = freqman_invalid_index;
@@ -609,12 +607,12 @@ ReconView::ReconView(NavigationView& nav)
scanner_mode = false;
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
button_remove.set_text("DELETE");
button_remove.set_text("REMOVE");
} else {
scanner_mode = true;
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
button_scanner_mode.set_text("REMOVE");
button_remove.set_text("DELETE");
}
frequency_file_load(true);
if (autostart) {
@@ -622,12 +620,17 @@ ReconView::ReconView(NavigationView& nav)
} else {
recon_pause();
}
button_add.hidden(scanner_mode);
if (scanner_mode) // only needed when hiding, UI mayhem
set_dirty();
};
button_config.on_select = [this, &nav](Button&) {
if (is_recording) // disabling config while recording
return;
clear_freqlist_for_ui_action();
freq_lock = 0;
timer = 0;
auto open_view = nav.push<ReconSetupView>(input_file, output_file);
@@ -734,30 +737,22 @@ void ReconView::frequency_file_load(bool) {
button_scanner_mode.set_text("RECON");
}
file_name.set(file_input + "=>" + output_file);
freqman_load_options options{
.load_freqs = load_freqs,
.load_ranges = load_ranges,
.load_hamradios = load_hamradios};
if (!load_freqman_file(file_input, frequency_list, options)) {
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
file_name.set_style(&Styles::red);
desc_cycle.set(" NO " + file_input + ".TXT FILE ...");
file_name.set("=> NO DATA");
} else {
file_name.set(file_input + "=>" + output_file);
if (frequency_list.size() == 0) {
file_name.set_style(&Styles::red);
desc_cycle.set("/0 no entries in list");
file_name.set("BadOrEmpty " + file_input);
} else {
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
}
desc_cycle.set("...empty file...");
frequency_list.clear();
text_cycle.set_text(" ");
return;
}
if (frequency_list.empty()) {
text_cycle.set_text(" ");
return; // Can't really do much.
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
reset_indexes();
@@ -782,19 +777,8 @@ void ReconView::frequency_file_load(bool) {
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
systime_t time_interval = 100;
uint32_t local_recon_lock_duration = recon_lock_duration;
chrono_end = chTimeNow();
if (field_mode.selected_index_value() == SPEC_MODULATION) {
time_interval = chrono_end - chrono_start;
if (field_lock_wait.value() == 0) {
if (time_interval <= 1) // capping here to avoid freeze because too quick
local_recon_lock_duration = 2; // minimum working tested value
else
local_recon_lock_duration = time_interval;
}
}
systime_t time_interval = chrono_end - chrono_start;
chrono_start = chrono_end;
// hack to reload the list if it was cleared by going into CONFIG
@@ -814,7 +798,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (!timer) {
status = 0;
freq_lock = 0;
timer = local_recon_lock_duration;
timer = recon_lock_duration;
}
if (freq_lock < recon_lock_nb_match) // LOCKING
{
@@ -1215,14 +1199,6 @@ void ReconView::handle_remove_current_item() {
if (current_is_valid()) {
frequency_list.erase(frequency_list.begin() + current_index);
}
if (frequency_list.size() > 0) {
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
update_description();
} else {
current_index = 0;
}
}
// In Scanner or Manual mode, remove the entry from the output file.
@@ -1233,6 +1209,17 @@ void ReconView::handle_remove_current_item() {
freq_db.delete_entry(entry);
}
// Clip
if (frequency_list.size() > 0) {
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
} else {
current_index = 0;
text_cycle.set_text(" ");
}
update_description();
}
} /* namespace ui */
+1 -1
View File
@@ -303,7 +303,7 @@ class ReconView : public View {
NumberField field_lock_wait{
{25 * 8, 24 * 8 + 4},
4,
{0, RECON_MAX_LOCK_DURATION},
{STATS_UPDATE_INTERVAL, RECON_MAX_LOCK_DURATION},
STATS_UPDATE_INTERVAL,
' ',
};
+574 -14
View File
@@ -1,6 +1,5 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -22,30 +21,591 @@
#include "ui_remote.hpp"
#include "baseband_api.hpp"
#include "binder.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "io_convert.hpp"
#include "irq_controls.hpp"
#include "oversample.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "ui_receiver.hpp"
#include "ui_textentry.hpp"
#include "utility.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
void RemoteView::focus() {
button.focus();
static constexpr uint8_t text_edit_max = 30;
/* RemoteEntryModel **************************************/
std::string RemoteEntryModel::to_string() const {
return join(',', {path.string(),
name,
to_string_dec_uint(icon),
to_string_dec_uint(bg_color),
to_string_dec_uint(fg_color),
to_string_dec_uint(metadata.center_frequency),
to_string_dec_uint(metadata.sample_rate)});
}
RemoteView::~RemoteView() {
// transmitter_model.disable();
// baseband::shutdown();
Optional<RemoteEntryModel> RemoteEntryModel::parse(std::string_view line) {
auto cols = split_string(line, ',');
if (cols.size() < 7)
return {};
RemoteEntryModel entry{};
entry.path = cols[0];
entry.name = std::string{cols[1]};
parse_int(cols[2], entry.icon);
parse_int(cols[3], entry.bg_color);
parse_int(cols[4], entry.fg_color);
parse_int(cols[5], entry.metadata.center_frequency);
parse_int(cols[6], entry.metadata.sample_rate);
return entry;
}
RemoteView::RemoteView(
NavigationView& nav) {
add_children({&labels,
&button});
/* RemoteModel *******************************************/
button.on_select = [this, &nav](Button&) {
nav.pop();
bool RemoteModel::delete_entry(const RemoteEntryModel* entry) {
// NB: expecting 'entry' to be a pointer to an entry in vector.
auto it = std::find_if(
entries.begin(), entries.end(),
[entry](auto& item) { return entry == &item; });
if (it == entries.end())
return false;
entries.erase(it);
return true;
}
bool RemoteModel::load(const std::filesystem::path& path) {
File f;
auto error = f.open(path);
if (error)
return false;
entries.clear();
bool first = true;
auto reader = FileLineReader(f);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue; // Empty or comment line.
// First line is the "name" field.
if (first) {
name = trim(line);
first = false;
continue;
}
// All the other lines are button entries.
auto entry = RemoteEntryModel::parse(line);
if (entry)
entries.push_back(*std::move(entry));
}
return true;
}
bool RemoteModel::save(const std::filesystem::path& path) {
File f;
auto error = f.create(path);
if (error)
return false;
f.write_line(name);
for (auto& entry : entries)
f.write_line(entry.to_string());
return true;
}
/* RemoteButton ******************************************/
RemoteButton::RemoteButton(Rect parent_rect, RemoteEntryModel* entry)
: NewButton{parent_rect, {}, nullptr},
entry_{nullptr} {
set_entry(entry);
// Forward to on_select2 -- this isn't ideal, but works for now.
on_select = [this]() {
if (on_select2)
on_select2(*this);
};
}
void RemoteButton::on_focus() {
// Enable long press on "Select".
SwitchesState config;
config[toUType(Switch::Sel)] = true;
set_switches_long_press_config(config);
}
void RemoteButton::on_blur() {
// Reset long press.
SwitchesState config{};
set_switches_long_press_config(config);
}
bool RemoteButton::on_key(KeyEvent key) {
if (key == KeyEvent::Select) {
if (key_is_long_pressed(key) && on_long_select) {
on_long_select(*this);
return true;
}
if (on_select2) {
on_select2(*this);
return true;
}
}
return false;
}
void RemoteButton::paint(Painter& painter) {
NewButton::paint(painter);
// Add a border on the highlighted button.
if (has_focus() || highlighted()) {
auto r = screen_rect();
painter.draw_rectangle(r, Color::white());
auto p = r.location() + Point{1, 1};
auto s = Size{r.size().width() - 2, r.size().height() - 2};
painter.draw_rectangle({p, s}, Color::light_grey());
}
};
RemoteEntryModel* RemoteButton::entry() {
return entry_;
}
void RemoteButton::set_entry(RemoteEntryModel* entry) {
entry_ = entry;
set_focusable(entry_ != nullptr);
hidden(entry_ == nullptr);
if (entry_) {
set_text(entry_->name);
set_bitmap(RemoteIcons::get(entry_->icon));
}
set_dirty();
}
Style RemoteButton::paint_style() {
if (!entry_)
return style();
MutableStyle s{style()};
s.foreground = RemoteColors::get(entry_->fg_color);
s.background = RemoteColors::get(entry_->bg_color);
if (has_focus() || highlighted())
s.invert();
// It's kind of a hack to set 'color_' here, but the base
// class' paint logic is kind of convoluted but isn't worth
// the extra bytes to copy and paste a paint override.
color_ = s.foreground;
return s;
}
/* RemoteEntryEditView ***********************************/
RemoteEntryEditView::RemoteEntryEditView(
NavigationView& nav,
RemoteEntryModel& entry)
: entry_{entry} {
add_children({
&labels,
&field_name,
&field_path,
&field_freq,
&text_rate,
&field_icon_index,
&field_fg_color_index,
&field_bg_color_index,
&button_preview,
&button_delete,
&button_done,
});
bind(field_name, entry_.name, nav, [this](auto& v) {
button_preview.set_text(v);
});
field_path.on_select = [this, &nav](TextField&) {
auto open_view = nav.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
load_path(std::move(path));
refresh_ui();
};
};
bind(field_freq, entry_.metadata.center_frequency, nav);
bind(field_icon_index, entry_.icon, [this](auto v) {
button_preview.set_bitmap(RemoteIcons::get(v));
});
bind(field_fg_color_index, entry_.fg_color, [this](auto v) {
button_preview.set_color(RemoteColors::get(v));
});
bind(field_bg_color_index, entry_.bg_color, [this](auto) {
button_preview.set_dirty();
});
button_delete.on_select = [this, &nav]() {
nav.display_modal(
"Delete?", " Delete this button?", YESNO,
[this, &nav](bool choice) {
if (choice) {
if (on_delete)
on_delete(entry_);
// Exit the edit view upon delete.
nav.set_on_pop([&nav]() { nav.pop(); });
}
});
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
refresh_ui();
}
void RemoteEntryEditView::focus() {
button_done.focus();
}
void RemoteEntryEditView::refresh_ui() {
field_path.set_text(entry_.path.filename().string());
field_freq.set_value(entry_.metadata.center_frequency);
text_rate.set(unit_auto_scale(entry_.metadata.sample_rate, 3, 0) + "Hz");
}
void RemoteEntryEditView::load_path(std::filesystem::path&& path) {
// Read metafile if it exists.
auto metadata_path = get_metadata_path(path);
auto metadata = read_metadata_file(metadata_path);
entry_.path = std::move(path);
// Use metadata if found, otherwise fallback to the TX frequency.
if (metadata)
entry_.metadata = *metadata;
else
entry_.metadata = {transmitter_model.target_frequency(), 500'000};
}
/* RemoteView ********************************************/
RemoteView::RemoteView(
NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_replay);
add_children({
&field_title,
&tx_view,
&check_loop,
&field_filename,
&button_add,
&button_new,
&button_open,
&waterfall,
});
create_buttons();
field_title.on_select = [this, &nav](TextField&) {
temp_buffer_ = model_.name;
text_prompt(nav_, temp_buffer_, text_edit_max, [this](std::string& new_name) {
model_.name = new_name;
refresh_ui();
set_needs_save();
});
};
field_filename.on_select = [this, &nav](TextField&) {
temp_buffer_ = remote_path_.stem().string();
text_prompt(nav_, temp_buffer_, text_edit_max, [this](std::string& new_name) {
rename_remote(new_name);
refresh_ui();
});
};
button_add.on_select = [this]() { add_button(); };
button_new.on_select = [this]() { new_remote(); };
button_open.on_select = [this]() { open_remote(); };
// Fill in the area between the remote buttons and bottom UI with waterfall.
Dim waterfall_top = buttons_top_.y() + button_area_height;
Dim waterfall_bottom = button_add.parent_rect().top();
Dim waterfall_height = waterfall_bottom - waterfall_top;
waterfall.set_parent_rect({0, waterfall_top, screen_width, waterfall_height});
ensure_directory(u"REMOTES");
// Load the previously loaded remote if exists.
if (!load_remote(settings_.remote_path))
init_remote();
refresh_ui();
}
RemoteView::RemoteView(NavigationView& nav, fs::path path)
: RemoteView(nav) {
load_remote(std::move(path));
refresh_ui();
}
RemoteView::~RemoteView() {
stop();
baseband::shutdown();
save_remote(/*show_error*/ false);
}
void RemoteView::focus() {
if (model_.entries.empty())
button_add.focus();
else
buttons_[0]->focus();
}
void RemoteView::create_buttons() {
// Handler callbacks.
auto handle_send = [this](RemoteButton& btn) {
if (btn.entry()->path.empty())
// No path set? Go to edit mode instead.
edit_button(btn);
else if (is_sending() && &btn == current_btn_)
// Pressed the same button again? Stop.
stop();
else
// Start sending.
send_button(btn);
};
auto handle_edit = [this](RemoteButton& btn) {
edit_button(btn);
};
// Create and add RemoteButtons for the whole grid.
for (size_t i = 0; i < max_buttons; ++i) {
Coord x = i % button_cols;
Coord y = i / button_cols;
Point pos = Point{x * button_width, y * button_height} + buttons_top_;
auto btn = std::make_unique<RemoteButton>(
Rect{pos, {button_width, button_height}},
nullptr);
btn->on_select2 = handle_send;
btn->on_long_select = handle_edit;
add_child(btn.get());
buttons_.push_back(std::move(btn));
}
}
void RemoteView::reset_buttons() {
// Whever the model's entries instance is invalidated,
// all the pointers in the buttons will end up dangling.
// TODO: This is pretty lame. Could maybe static alloc?
for (auto& btn : buttons_)
btn->set_entry(nullptr);
}
void RemoteView::refresh_ui() {
field_title.set_text(model_.name);
field_filename.set_text(remote_path_.stem().string());
// Update buttons from the model.
for (size_t i = 0; i < buttons_.size(); ++i) {
if (i < model_.entries.size())
buttons_[i]->set_entry(&model_.entries[i]);
else
buttons_[i]->set_entry(nullptr);
}
}
void RemoteView::add_button() {
if (model_.entries.size() >= max_buttons)
return;
// Don't let replay thread read the model while editing.
stop();
model_.entries.push_back({{}, "<EMPTY>", 0, 3, 1});
reset_buttons();
refresh_ui();
set_needs_save();
}
void RemoteView::edit_button(RemoteButton& btn) {
// Don't let replay thread read the model while editing.
stop();
auto edit_view = nav_.push<RemoteEntryEditView>(*btn.entry());
nav_.set_on_pop([this]() {
refresh_ui();
set_needs_save();
focus(); // Need to refocus after refreshing the buttons.
});
edit_view->on_delete = [this](RemoteEntryModel& to_delete) {
model_.delete_entry(&to_delete);
reset_buttons();
};
}
void RemoteView::send_button(RemoteButton& btn) {
// TODO: If this is called while is_sending() == true,
// it just stops and doesn't start the new button?
// Reset everything to prepare to send a file.
stop();
current_btn_ = &btn; // Stash for looping.
// Open the sample file to send.
auto reader = std::make_unique<FileConvertReader>();
auto error = reader->open(btn.entry()->path);
if (error) {
show_error("Can't open file:\n" + btn.entry()->path.stem().string());
return;
}
// Update the sample rate in proc_replay baseband.
baseband::set_sample_rate(
btn.entry()->metadata.sample_rate,
get_oversample_rate(btn.entry()->metadata.sample_rate));
// ReplayThread starts immediately on construction; must be set before creating.
transmitter_model.set_target_frequency(btn.entry()->metadata.center_frequency);
transmitter_model.set_sampling_rate(get_actual_sample_rate(btn.entry()->metadata.sample_rate));
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.enable();
// ReplayThread reads the file and sends to the baseband.
replay_thread_ = std::make_unique<ReplayThread>(
std::move(reader),
/* read_size */ 0x4000,
/* buffer_count */ 3,
&ready_signal_,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
}
void RemoteView::stop() {
// This terminates the underlying chThread.
replay_thread_.reset();
transmitter_model.disable();
ready_signal_ = false;
}
void RemoteView::new_remote() {
save_remote();
init_remote();
refresh_ui();
// View needs to redraw to hide old buttons.
set_dirty();
}
void RemoteView::open_remote() {
auto open_view = nav_.push<FileLoadView>(".REM");
open_view->push_dir(u"REMOTES");
open_view->on_changed = [this](fs::path path) {
save_remote();
load_remote(std::move(path));
refresh_ui();
};
}
void RemoteView::init_remote() {
model_ = {"<Unnamed Remote>", {}};
reset_buttons();
set_remote_path(next_filename_matching_pattern(u"/REMOTES/REMOTE_????.REM"));
set_needs_save(false);
if (remote_path_.empty())
show_error("Couldn't make new remote file.");
}
bool RemoteView::load_remote(fs::path&& path) {
set_remote_path(std::move(path));
set_needs_save(false);
reset_buttons();
return model_.load(remote_path_);
}
void RemoteView::save_remote(bool show_errors) {
if (!needs_save_)
return;
bool ok = model_.save(remote_path_);
if (!ok && show_errors)
show_error("Save failed for:\n" + remote_path_.stem().string());
set_needs_save(false);
}
void RemoteView::rename_remote(const std::string& new_name) {
auto folder = remote_path_.parent_path();
auto ext = remote_path_.extension();
auto new_path = folder / new_name + ext;
if (file_exists(new_path)) {
show_error("Remote " + new_name + " already exists");
return;
}
// Rename file if there is one.
if (fs::file_exists(remote_path_))
rename_file(remote_path_, new_path);
set_remote_path(std::move(new_path));
}
void RemoteView::handle_replay_thread_done(uint32_t return_code) {
if (return_code == ReplayThread::END_OF_FILE) {
if (check_loop.value() && current_btn_) {
send_button(*current_btn_);
return;
}
}
/*
// TODO: This can happen when stopping an in-progress Tx.
if (return_code == ReplayThread::READ_ERROR)
show_error("Bad capture file.");
*/
stop();
}
void RemoteView::set_remote_path(fs::path&& path) {
// Unfortunately, have to keep these two in sync because
// settings doesn't know about fs::path.
remote_path_ = std::move(path);
settings_.remote_path = remote_path_.string();
}
void RemoteView::show_error(const std::string& msg) const {
nav_.display_modal("Error", msg);
}
} /* namespace ui */
+347 -21
View File
@@ -1,6 +1,5 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -21,42 +20,369 @@
*/
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
#include "ui_transmitter.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "baseband_api.hpp"
#include "bitmap.hpp"
#include "file.hpp"
#include "metadata_file.hpp"
#include "optional.hpp"
#include "radio_state.hpp"
#include "replay_thread.hpp"
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace ui {
/* Maps icon index to bitmap. */
class RemoteIcons {
public:
static constexpr const Bitmap* get(uint8_t index) {
if (index >= std::size(bitmaps_))
return bitmaps_[0];
return bitmaps_[index];
}
static constexpr size_t size() {
return std::size(bitmaps_);
}
private:
// NB: Icons need to be 16x16 or they won't fit corrently.
static constexpr std::array<const Bitmap*, 25> bitmaps_{
nullptr,
&bitmap_icon_fox,
&bitmap_icon_adsb,
&bitmap_icon_ais,
&bitmap_icon_aprs,
&bitmap_icon_btle,
&bitmap_icon_burger,
&bitmap_icon_camera,
&bitmap_icon_cwgen,
&bitmap_icon_dmr,
&bitmap_icon_file_image,
&bitmap_icon_lge,
&bitmap_icon_looking,
&bitmap_icon_memory,
&bitmap_icon_morse,
&bitmap_icon_nrf,
&bitmap_icon_notepad,
&bitmap_icon_rds,
&bitmap_icon_remote,
&bitmap_icon_setup,
&bitmap_icon_sleep,
&bitmap_icon_sonde,
&bitmap_icon_stealth,
&bitmap_icon_tetra,
&bitmap_icon_temperature};
};
// TODO: Use RGB colors instead?
/* Maps color index to color. */
class RemoteColors {
public:
static constexpr Color get(uint8_t index) {
if (index >= std::size(colors_))
return colors_[0];
return colors_[index];
}
static constexpr size_t size() {
return std::size(colors_);
}
private:
static constexpr std::array<Color, 21> colors_{
Color::black(), // 0
Color::white(), // 1
Color::darker_grey(), // 2
Color::dark_grey(), // 3
Color::grey(), // 4
Color::light_grey(), // 5
Color::red(), // 6
Color::orange(), // 7
Color::yellow(), // 8
Color::green(), // 9
Color::blue(), // 10
Color::cyan(), // 11
Color::magenta(), // 12
Color::dark_red(), // 13
Color::dark_orange(), // 14
Color::dark_yellow(), // 15
Color::dark_green(), // 16
Color::dark_blue(), // 17
Color::dark_cyan(), // 18
Color::dark_magenta(), // 19
Color::purple()}; // 20
};
/* Data model for a remote entry. */
struct RemoteEntryModel {
std::filesystem::path path{};
std::string name{};
uint8_t icon = 0;
uint8_t bg_color = 0;
uint8_t fg_color = 0;
capture_metadata metadata{};
// TODO: start/end position for trimming.
std::string to_string() const;
static Optional<RemoteEntryModel> parse(std::string_view line);
};
/* Data model for a remote. */
struct RemoteModel {
std::string name{};
std::vector<RemoteEntryModel> entries{};
bool delete_entry(const RemoteEntryModel* entry);
bool load(const std::filesystem::path& path);
bool save(const std::filesystem::path& path);
};
/* Button for the remote UI. */
class RemoteButton : public NewButton {
public:
std::function<void(RemoteButton&)> on_select2{};
std::function<void(RemoteButton&)> on_long_select{};
RemoteButton(Rect parent_rect, RemoteEntryModel* entry);
RemoteButton(const RemoteButton&) = delete;
RemoteButton& operator=(const RemoteButton&) = delete;
void on_focus() override;
void on_blur() override;
bool on_key(KeyEvent key) override;
void paint(Painter& painter) override;
RemoteEntryModel* entry();
void set_entry(RemoteEntryModel* entry);
protected:
Style paint_style() override;
private:
// Hide because it's not used.
using NewButton::on_select;
RemoteEntryModel* entry_;
};
/* Settings container for remote. */
struct RemoteSettings {
std::string remote_path{};
};
/* View for editing a remote entry button. */
class RemoteEntryEditView : public View {
public:
std::function<void(RemoteEntryModel&)> on_delete{};
RemoteEntryEditView(NavigationView& nav, RemoteEntryModel& entry);
std::string title() const override { return "Edit Button"; };
void focus() override;
private:
RemoteEntryModel& entry_;
void refresh_ui();
void load_path(std::filesystem::path&& path);
Labels labels{
{{2 * 8, 1 * 16}, "Name:", Color::light_grey()},
{{2 * 8, 2 * 16}, "Path:", Color::light_grey()},
{{2 * 8, 3 * 16}, "Freq:", Color::light_grey()},
{{17 * 8, 3 * 16}, "MHz", Color::light_grey()},
{{2 * 8, 4 * 16}, "Rate:", Color::light_grey()},
{{2 * 8, 5 * 16}, "Icon:", Color::light_grey()},
{{2 * 8, 6 * 16}, "FG Color:", Color::light_grey()},
{{2 * 8, 7 * 16}, "BG Color:", Color::light_grey()},
{{8 * 8, 9 * 16}, "Button preview", Color::light_grey()},
};
TextField field_name{{8 * 8, 1 * 16, 20 * 8, 1 * 16}, {}};
TextField field_path{{8 * 8, 2 * 16, 20 * 8, 1 * 16}, {}};
FrequencyField field_freq{{8 * 8, 3 * 16}};
Text text_rate{{8 * 8, 4 * 16, 20 * 8, 1 * 16}, {}};
NumberField field_icon_index{
{8 * 8, 5 * 16},
2,
{0, RemoteIcons::size() - 1},
/*step*/ 1,
/*fill*/ ' ',
/*loop*/ true};
NumberField field_fg_color_index{
{11 * 8, 6 * 16},
2,
{0, RemoteColors::size() - 1},
/*step*/ 1,
/*fill*/ ' ',
/*loop*/ true};
NumberField field_bg_color_index{
{11 * 8, 7 * 16},
2,
{0, RemoteColors::size() - 1},
/*step*/ 1,
/*fill*/ ' ',
/*loop*/ true};
RemoteButton button_preview{
{10 * 8, 11 * 16 - 8, 10 * 8, 50},
&entry_};
NewButton button_delete{
{2 * 8, 16 * 16, 4 * 8, 2 * 16},
{},
&bitmap_icon_trash,
Color::red()};
Button button_done{{11 * 8, 16 * 16, 8 * 8, 2 * 16}, "Done"};
};
/* App that allows for buttons to be bound to captures for playback. */
class RemoteView : public View {
public:
RemoteView(NavigationView& nav);
RemoteView(NavigationView& nav, std::filesystem::path path);
~RemoteView();
RemoteView(const RemoteView&) = delete;
RemoteView& operator=(const RemoteView&) = delete;
std::string title() const override { return "Remote"; };
void focus() override;
std::string title() const override { return "Custom remote"; };
private:
/*enum tx_modes {
IDLE = 0,
SINGLE,
SCAN
};
/* Creates the dynamic buttons. */
void create_buttons();
/* Resets all the pointers to null entries. */
void reset_buttons();
void refresh_ui();
tx_modes tx_mode = IDLE;
void add_button();
void edit_button(RemoteButton& btn);
void send_button(RemoteButton& btn);
void stop();
void new_remote();
void open_remote();
void init_remote();
bool load_remote(std::filesystem::path&& path);
void save_remote(bool show_errors = true);
void rename_remote(const std::string& new_name);
void handle_replay_thread_done(uint32_t return_code);
void set_needs_save(bool v = true) { needs_save_ = v; }
void set_remote_path(std::filesystem::path&& path);
bool is_sending() const { return replay_thread_ != nullptr; }
void show_error(const std::string& msg) const;
struct remote_layout_t {
Point position;
std::string text;
};
static constexpr Dim button_rows = 4;
static constexpr Dim button_cols = 3;
static constexpr uint8_t max_buttons = button_rows * button_cols;
static constexpr Dim button_area_height = 200;
static constexpr Dim button_width = screen_width / button_cols;
static constexpr Dim button_height = button_area_height / button_rows;
const std::array<remote_layout_t, 32> remote_layout { };*/
// This value is mysterious... why?
static constexpr uint32_t baseband_bandwidth = 2'500'000;
Labels labels{
{{1 * 8, 0}, "Work in progress...", Color::light_grey()}};
NavigationView& nav_;
RxRadioState radio_state_{};
Button button{
{60, 64, 120, 32},
"Exit"};
// Settings
RemoteSettings settings_{};
app_settings::SettingsManager app_settings_{
"tx_remote"sv,
app_settings::Mode::TX,
{
{"remote_path"sv, &settings_.remote_path},
}};
RemoteModel model_{};
bool needs_save_ = false;
std::string temp_buffer_{};
std::filesystem::path remote_path_{};
RemoteButton* current_btn_{};
const Point buttons_top_{0, 20};
std::vector<std::unique_ptr<RemoteButton>> buttons_{};
std::unique_ptr<ReplayThread> replay_thread_{};
bool ready_signal_{}; // Used to signal ReplayThread ready.
TextField field_title{
{0 * 8, 0 * 16 + 2, 30 * 8, 1 * 16},
{}};
TransmitterView2 tx_view{
{0 * 8, 17 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{10 * 8, 17 * 16},
4,
"Loop",
/*small*/ true};
TextField field_filename{
{0 * 8, 18 * 16, 17 * 8, 1 * 16},
{}};
NewButton button_add{
{17 * 8 + 4, 17 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_add,
Color::orange(),
/*vcenter*/ true};
NewButton button_new{
{22 * 8, 17 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_new_file,
Color::dark_blue(),
/*vcenter*/ true};
NewButton button_open{
{26 * 8, 17 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_load,
Color::dark_blue(),
/*vcenter*/ true};
spectrum::WaterfallView waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* p) {
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
handle_replay_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* p) {
auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
ready_signal_ = true;
}
}};
};
} /* namespace ui */
+11 -5
View File
@@ -44,15 +44,21 @@ void WipeSDView::focus() {
dummy.focus();
if (!confirmed) {
nav_.push<ModalMessageView>("Warning !", "Wipe FAT of SD card?", YESCANCEL, [this](bool choice) {
if (choice)
confirmed = true;
});
nav_.push<ModalMessageView>(
"Warning !",
"Wipe FAT of SD card?",
YESNO,
[this](bool choice) {
if (choice)
confirmed = true;
else
nav_.pop(false); // Pop w/o update so the modal will pop off the app.
});
} else {
if (sdcGetInfo(&SDCD1, &block_device_info) == CH_SUCCESS) {
thread = chThdCreateFromHeap(NULL, 2048, NORMALPRIO, WipeSDView::static_fn, this);
} else {
nav_.pop(); // Just silently abort for now
nav_.pop(); // Just silently abort for now.
}
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ class WipeSDView : public View {
~WipeSDView();
void focus() override;
std::string title() const override { return "Wipe sdcard"; };
std::string title() const override { return "Wipe SD Card"; };
private:
NavigationView& nav_;
+124 -157
View File
@@ -23,7 +23,9 @@
#include "ui_search.hpp"
#include "baseband_api.hpp"
#include "binder.hpp"
#include "string_format.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
@@ -47,8 +49,61 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
painter.draw_string(target_rect.location(), style, to_string_short_freq(entry.frequency) + " " + entry.time + " " + str_duration);
}
void SearchView::focus() {
field_frequency_min.focus();
/* SearchView ********************************************/
SearchView::SearchView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({&labels,
&field_frequency_min,
&field_frequency_max,
&field_lna,
&field_vga,
&field_threshold,
&text_mean,
&text_slices,
&text_rate,
&text_infos,
&vu_max,
&progress_timers,
&check_snap,
&options_snap,
&big_display,
&recent_entries_view});
baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);
recent_entries_view.set_parent_rect({0, 28 * 8, screen_width, 12 * 8});
recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
nav.push<FrequencySaveView>(entry.frequency);
};
text_mean.set_style(&Styles::grey);
text_slices.set_style(&Styles::grey);
text_rate.set_style(&Styles::grey);
progress_timers.set_style(&Styles::grey);
big_display.set_style(&Styles::grey);
field_frequency_min.set_step(100'000);
bind(field_frequency_min, settings_.freq_min, nav, [this](auto) {
on_range_changed();
});
field_frequency_max.set_step(100'000);
bind(field_frequency_max, settings_.freq_max, nav, [this](auto) {
on_range_changed();
});
bind(field_threshold, settings_.power_threshold);
bind(check_snap, settings_.snap_search);
bind(options_snap, settings_.snap_step);
progress_timers.set_max(DETECT_DELAY);
on_range_changed();
receiver_model.enable();
}
SearchView::~SearchView() {
@@ -56,6 +111,18 @@ SearchView::~SearchView() {
baseband::shutdown();
}
void SearchView::on_show() {
baseband::spectrum_streaming_start();
}
void SearchView::on_hide() {
baseband::spectrum_streaming_stop();
}
void SearchView::focus() {
field_frequency_min.focus();
}
void SearchView::do_detection() {
uint8_t power_max = 0;
int32_t bin_max = -1;
@@ -83,7 +150,7 @@ void SearchView::do_detection() {
if (power > overall_power_max)
overall_power_max = power;
if ((power >= mean_power + power_threshold) && (power > power_max)) {
if ((power >= mean_power + settings_.power_threshold) && (power > power_max)) {
power_max = power;
bin_max = slices[slice].max_index;
slice_max = slice;
@@ -104,7 +171,7 @@ void SearchView::do_detection() {
}
// Check range
if ((resolved_frequency >= f_min) && (resolved_frequency <= f_max)) {
if ((resolved_frequency >= settings_.freq_min) && (resolved_frequency <= settings_.freq_max)) {
duration = 0;
auto& entry = ::on_packet(recent, resolved_frequency);
@@ -159,16 +226,47 @@ void SearchView::do_detection() {
}
}
void SearchView::add_spectrum_pixel(Color color) {
// Is avoiding floats really necessary ?
bin_skip_acc += bin_skip_frac;
if (bin_skip_acc < 0x10000)
return;
void SearchView::do_timers() {
if (timing_div >= 60) {
// ~1Hz
bin_skip_acc -= 0x10000;
timing_div = 0;
if (pixel_index < 240)
spectrum_row[pixel_index++] = color;
// Update scan rate
text_rate.set(to_string_dec_uint(search_counter, 3));
search_counter = 0;
}
if (timing_div % 12 == 0) {
// ~5Hz
// Update power levels
text_mean.set(to_string_dec_uint(mean_power, 3));
vu_max.set_value(overall_power_max);
vu_max.set_mark(mean_power + settings_.power_threshold);
}
if (timing_div % 6 == 0) {
// ~10Hz
// Update timing indicator
if (locked) {
progress_timers.set_max(RELEASE_DELAY);
progress_timers.set_value(RELEASE_DELAY - release_timer);
} else {
progress_timers.set_max(DETECT_DELAY);
progress_timers.set_value(detect_timer);
}
// Increment timers
if (detect_timer < DETECT_DELAY) detect_timer++;
if (release_timer < RELEASE_DELAY) release_timer++;
if (locked) duration++;
}
timing_div++;
}
void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
@@ -221,22 +319,14 @@ void SearchView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_start();
}
void SearchView::on_show() {
baseband::spectrum_streaming_start();
}
void SearchView::on_hide() {
baseband::spectrum_streaming_stop();
}
void SearchView::on_range_changed() {
rf::Frequency slices_span, center_frequency;
rf::Frequency slices_span;
rf::Frequency center_frequency;
int64_t offset;
size_t slice;
f_min = field_frequency_min.value();
f_max = field_frequency_max.value();
search_span = abs(f_max - f_min);
// TODO: enforce min < max?
search_span = abs(settings_.freq_max - settings_.freq_min);
if (search_span > SEARCH_SLICE_WIDTH) {
// ex: 100M~115M (15M span):
@@ -253,14 +343,14 @@ void SearchView::on_range_changed() {
// offset = 0 + 2.5/2 = 1.25M
offset = ((search_span - slices_span) / 2) + (SEARCH_SLICE_WIDTH / 2);
// slice_start = 100M + 1.25M = 101.25M
center_frequency = std::min(f_min, f_max) + offset;
center_frequency = std::min(settings_.freq_min, settings_.freq_max) + offset;
for (slice = 0; slice < slices_nb; slice++) {
slices[slice].center_frequency = center_frequency;
center_frequency += SEARCH_SLICE_WIDTH;
}
} else {
slices[0].center_frequency = (f_max + f_min) / 2;
slices[0].center_frequency = (settings_.freq_max + settings_.freq_min) / 2;
receiver_model.set_target_frequency(slices[0].center_frequency);
slices_nb = 1;
@@ -272,139 +362,16 @@ void SearchView::on_range_changed() {
slice_counter = 0;
}
void SearchView::on_lna_changed(int32_t v_db) {
receiver_model.set_lna(v_db);
}
void SearchView::add_spectrum_pixel(Color color) {
// Is avoiding floats really necessary?
bin_skip_acc += bin_skip_frac;
if (bin_skip_acc < 0x10000)
return;
void SearchView::on_vga_changed(int32_t v_db) {
receiver_model.set_vga(v_db);
}
bin_skip_acc -= 0x10000;
void SearchView::do_timers() {
if (timing_div >= 60) {
// ~1Hz
timing_div = 0;
// Update scan rate
text_rate.set(to_string_dec_uint(search_counter, 3));
search_counter = 0;
}
if (timing_div % 12 == 0) {
// ~5Hz
// Update power levels
text_mean.set(to_string_dec_uint(mean_power, 3));
vu_max.set_value(overall_power_max);
vu_max.set_mark(mean_power + power_threshold);
}
if (timing_div % 6 == 0) {
// ~10Hz
// Update timing indicator
if (locked) {
progress_timers.set_max(RELEASE_DELAY);
progress_timers.set_value(RELEASE_DELAY - release_timer);
} else {
progress_timers.set_max(DETECT_DELAY);
progress_timers.set_value(detect_timer);
}
// Increment timers
if (detect_timer < DETECT_DELAY) detect_timer++;
if (release_timer < RELEASE_DELAY) release_timer++;
if (locked) duration++;
}
timing_div++;
}
SearchView::SearchView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({&labels,
&field_frequency_min,
&field_frequency_max,
&field_lna,
&field_vga,
&field_threshold,
&text_mean,
&text_slices,
&text_rate,
&text_infos,
&vu_max,
&progress_timers,
&check_snap,
&options_snap,
&big_display,
&recent_entries_view});
baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);
recent_entries_view.set_parent_rect({0, 28 * 8, 240, 12 * 8});
recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
nav.push<FrequencyKeypadView>(entry.frequency);
};
text_mean.set_style(&Styles::grey);
text_slices.set_style(&Styles::grey);
text_rate.set_style(&Styles::grey);
progress_timers.set_style(&Styles::grey);
big_display.set_style(&Styles::grey);
check_snap.set_value(true);
options_snap.set_selected_index(1); // 12.5kHz
field_threshold.set_value(80);
field_threshold.on_change = [this](int32_t value) {
power_threshold = value;
};
field_frequency_min.set_value(receiver_model.target_frequency() - 1000000);
field_frequency_min.set_step(100000);
field_frequency_min.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_min.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() - 1000000);
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_min.set_value(f);
};
};
field_frequency_max.set_value(receiver_model.target_frequency() + 1000000);
field_frequency_max.set_step(100000);
field_frequency_max.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_max.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.target_frequency() + 1000000);
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_max.set_value(f);
};
};
field_lna.set_value(receiver_model.lna());
field_lna.on_change = [this](int32_t v) {
this->on_lna_changed(v);
};
field_vga.set_value(receiver_model.vga());
field_vga.on_change = [this](int32_t v_db) {
this->on_vga_changed(v_db);
};
progress_timers.set_max(DETECT_DELAY);
on_range_changed();
receiver_model.enable();
if (pixel_index < screen_width)
spectrum_row[pixel_index++] = color;
}
} /* namespace ui */
+56 -31
View File
@@ -20,14 +20,13 @@
* Boston, MA 02110-1301, USA.
*/
#include "app_settings.hpp"
#include "receiver_model.hpp"
#include "recent_entries.hpp"
#include "radio_state.hpp"
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "recent_entries.hpp"
namespace ui {
@@ -91,10 +90,31 @@ class SearchView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{
100'000'000 /* frequency */,
2500000 /* bandwidth */,
SEARCH_SLICE_WIDTH /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
// Settings
struct SearchSettings {
uint32_t power_threshold = 80;
rf::Frequency freq_min = 100'000'000;
rf::Frequency freq_max = 400'000'000;
bool snap_search = true;
uint32_t snap_step = 12'500;
};
SearchSettings settings_{};
app_settings::SettingsManager app_settings_{
"rx_search"sv,
app_settings::Mode::RX,
{
{"power_threshold"sv, &settings_.power_threshold},
{"freq_min"sv, &settings_.freq_min},
{"freq_max"sv, &settings_.freq_max},
{"snap_search"sv, &settings_.snap_search},
{"snap_step"sv, &settings_.snap_step},
}};
struct slice_t {
rf::Frequency center_frequency;
uint8_t max_power;
@@ -103,33 +123,36 @@ class SearchView : public View {
int16_t index;
} slices[32];
uint32_t bin_skip_acc{0}, bin_skip_frac{};
uint32_t pixel_index{0};
std::array<Color, 240> spectrum_row = {0};
ChannelSpectrumFIFO* fifo{nullptr};
rf::Frequency f_min{0}, f_max{0};
uint8_t detect_timer{0}, release_timer{0}, timing_div{0};
uint8_t overall_power_max{0};
uint32_t mean_power{0}, mean_acc{0};
uint32_t duration{0};
uint32_t power_threshold{80}; // Todo: Put this in persistent / settings
rf::Frequency slice_start{0};
uint8_t slices_nb{0};
uint8_t slice_counter{0};
int16_t last_bin{0};
uint32_t last_slice{0};
Coord last_tick_pos{0};
rf::Frequency search_span{0}, resolved_frequency{0};
uint16_t locked_bin{0};
uint8_t search_counter{0};
bool locked{false};
uint32_t bin_skip_acc = 0;
uint32_t bin_skip_frac = 0;
uint32_t pixel_index = 0;
std::array<Color, 240> spectrum_row{};
ChannelSpectrumFIFO* fifo = nullptr;
uint8_t detect_timer = 0;
uint8_t release_timer = 0;
uint8_t timing_div = 0;
uint8_t overall_power_max{0};
uint32_t mean_power = 0;
uint32_t mean_acc = 0;
uint32_t duration = 0;
rf::Frequency slice_start = 0;
uint8_t slices_nb = 0;
uint8_t slice_counter = 0;
int16_t last_bin = 0;
uint32_t last_slice = 0;
Coord last_tick_pos = 0;
rf::Frequency search_span = 0;
rf::Frequency resolved_frequency = 0;
uint16_t locked_bin = 0;
uint8_t search_counter = 0;
bool locked = false;
void do_detection();
void do_timers();
void on_channel_spectrum(const ChannelSpectrum& spectrum);
void on_range_changed();
void do_detection();
void on_lna_changed(int32_t v_db);
void on_vga_changed(int32_t v_db);
void do_timers();
void add_spectrum_pixel(Color color);
const RecentEntriesColumns columns{{{"Frequency", 9},
@@ -150,6 +173,7 @@ class SearchView : public View {
{1 * 8, 1 * 16}};
FrequencyField field_frequency_max{
{11 * 8, 1 * 16}};
LNAGainField field_lna{
{22 * 8, 1 * 16}};
VGAGainField field_vga{
@@ -191,10 +215,10 @@ class SearchView : public View {
{17 * 8, 15 * 8}, // Position
7, // Length
{ // Options
{"25kHz ", 25000},
{"12.5kHz", 12500},
{"8.33kHz", 8333},
{"2.5kHz", 2500},
{"25kHz ", 25'000},
{"12.5kHz", 12'500},
{"8.33kHz", 8'333},
{"2.5kHz", 2'500},
{"500Hz", 500}}};
BigFrequency big_display{
@@ -207,6 +231,7 @@ class SearchView : public View {
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
this->fifo = message.fifo;
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
+7 -36
View File
@@ -343,34 +343,6 @@ void SetUIView::focus() {
button_save.focus();
}
/* SetAppSettingsView ************************************/
SetAppSettingsView::SetAppSettingsView(NavigationView& nav) {
add_children({
&labels,
&checkbox_load_app_settings,
&checkbox_save_app_settings,
&button_save,
&button_cancel,
});
checkbox_load_app_settings.set_value(pmem::load_app_settings());
checkbox_save_app_settings.set_value(pmem::save_app_settings());
button_save.on_select = [&nav, this](Button&) {
pmem::set_load_app_settings(checkbox_load_app_settings.value());
pmem::set_save_app_settings(checkbox_save_app_settings.value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetAppSettingsView::focus() {
button_save.focus();
}
/* SetConverterSettingsView ******************************/
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
@@ -655,16 +627,15 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
}
add_items({
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetRadioView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetAppSettingsView>(); }},
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
});
set_max_rows(2); // allow wider buttons
}
-35
View File
@@ -310,41 +310,6 @@ class SetUIView : public View {
};
};
class SetAppSettingsView : public View {
public:
SetAppSettingsView(NavigationView& nav);
void focus() override;
std::string title() const override { return "App Settings"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "App settings are saved to", Color::light_grey()},
{{1 * 8, 2 * 16}, "the SD card in /SETTINGS.", Color::light_grey()},
{{1 * 8, 3 * 16}, "Radio settings may also be", Color::light_grey()},
{{1 * 8, 4 * 16}, "loaded or saved per app.", Color::light_grey()},
};
Checkbox checkbox_load_app_settings{
{3 * 8, 6 * 16},
25,
"Load radio settings"};
Checkbox checkbox_save_app_settings{
{3 * 8, 8 * 16},
25,
"Save radio settings"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SetConverterSettingsView : public View {
public:
SetConverterSettingsView(NavigationView& nav);
+1
View File
@@ -51,6 +51,7 @@ class SigGenView : public View {
void on_tx_progress(const uint32_t progress, const bool done);
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
-3
View File
@@ -66,9 +66,6 @@ SondeView::SondeView(NavigationView& nav)
&button_see_qr,
&button_see_map});
if (!settings_.radio_loaded())
field_frequency.set_value(initial_target_frequency);
field_frequency.set_step(500); // euquiq: was 10000, but we are using this for fine-tunning
geopos.set_read_only(true);
+1 -1
View File
@@ -68,6 +68,7 @@ class SondeView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{
402700000 /* frequency */,
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
@@ -75,7 +76,6 @@ class SondeView : public View {
"rx_sonde", app_settings::Mode::RX};
std::unique_ptr<SondeLogger> logger{};
uint32_t target_frequency_{402700000};
bool logging{false};
bool use_crc{false};
bool beep{false};
@@ -85,7 +85,11 @@ SpectrumPainterView::SpectrumPainterView(
if (tx_mode == 0 && image_input_avaliable == false)
return;
/* By experimental test measurement, we got a good painted spectrum quality when selecting
a BW GUI App range from 100k ... 2M aprox. In that range , the best TX LPF filter = 1M75 (the min)
With this min TX LPF filter , we minimized the harmonics aliasing multipicture overlapped.
So original fw 1.7.4., was already selecting the best setting (1M75) */
transmitter_model.set_baseband_bandwidth(1'750'000); // Already tested, is the best setting for this App.
transmitter_model.enable();
if (persistent_memory::stealth_mode()) {
@@ -40,17 +40,13 @@ SpectrumInputImageView::SpectrumInputImageView(NavigationView& nav) {
auto open_view = nav.push<FileLoadView>(".bmp");
constexpr auto data_directory = u"SPECTRUM";
if (std::filesystem::is_directory(data_directory) == false) {
if (make_new_directory(data_directory).ok())
open_view->push_dir(data_directory);
} else
open_view->push_dir(data_directory);
ensure_directory(data_directory);
open_view->push_dir(data_directory);
open_view->on_changed = [this](std::filesystem::path new_file_path) {
this->file = new_file_path.string();
painted = false;
this->set_dirty();
this->on_input_avaliable();
};
};
+2 -1
View File
@@ -35,7 +35,7 @@ namespace ui {
void SSTVTXView::focus() {
if (file_error)
nav_.display_modal("No files", "No valid bitmaps\nin /sstv directory.", ABORT, nullptr);
nav_.display_modal("No files", "No valid bitmaps\nin /sstv directory.", ABORT);
else
options_bitmaps.focus();
}
@@ -160,6 +160,7 @@ void SSTVTXView::start_tx() {
scanline_counter = 0;
prepare_scanline(); // Preload one scanline
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF 1M75, same spectrum as previous fw 1.7.4
transmitter_model.enable();
baseband::set_sstv_data(
+1
View File
@@ -62,6 +62,7 @@ class TestView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
2457600 * 2 /* sampling rate */
};
+11 -26
View File
@@ -29,14 +29,6 @@
using namespace portapack;
namespace fs = std::filesystem;
namespace {
/*void log(const std::string& msg) {
LogFile log{};
log.append("LOGS/Notepad.txt");
log.write_entry(msg);
}*/
} // namespace
namespace ui {
/* TextViewer *******************************************************/
@@ -413,16 +405,9 @@ TextEditorView::TextEditorView(NavigationView& nav)
};
menu.on_open() = [this]() {
/*show_save_prompt([this]() {
show_save_prompt([this]() {
show_file_picker();
});*/
// HACK: above should work but it's faulting.
if (!file_dirty_) {
show_file_picker();
} else {
show_save_prompt(nullptr);
show_file_picker(false);
}
});
};
menu.on_save() = [this]() {
@@ -530,16 +515,16 @@ void TextEditorView::hide_menu(bool hidden) {
set_dirty();
}
void TextEditorView::show_file_picker(bool immediate) {
// TODO: immediate is a hack until nav_.on_pop is fixed.
auto open_view = immediate ? nav_.push<FileLoadView>("") : nav_.push_under_current<FileLoadView>("");
if (open_view) {
open_view->on_changed = [this](std::filesystem::path path) {
open_file(path);
void TextEditorView::show_file_picker() {
auto open_view = nav_.push<FileLoadView>("");
open_view->on_changed = [this](std::filesystem::path path) {
// Can't update the UI focus while the FileLoadView is still up.
// Do this on a continuation instead of in on_changed.
nav_.set_on_pop([this, p = std::move(path)]() {
open_file(p);
hide_menu();
};
}
});
};
}
void TextEditorView::show_edit_line() {
+5 -8
View File
@@ -19,10 +19,6 @@
* Boston, MA 02110-1301, USA.
*/
/* TODO:
* - Busy indicator while reading files.
*/
#ifndef __UI_TEXT_EDITOR_H__
#define __UI_TEXT_EDITOR_H__
@@ -238,7 +234,7 @@ class TextEditorView : public View {
void refresh_ui();
void update_position();
void hide_menu(bool hidden = true);
void show_file_picker(bool immediate = true);
void show_file_picker();
void show_edit_line();
void show_save_prompt(std::function<void()> continuation);
@@ -252,8 +248,8 @@ class TextEditorView : public View {
bool has_temp_file_{false};
TextViewer viewer{
/* 272 = 320 - 16 (top bar) - 32 (bottom controls) */
{0, 0, 240, 272}};
/* 272 = screen_height - 16 (top bar) - 32 (bottom controls) */
{0, 0, screen_width, 272}};
TextEditorMenu menu{};
@@ -261,7 +257,8 @@ class TextEditorView : public View {
{26 * 8, 34 * 8, 4 * 8, 4 * 8},
{},
&bitmap_icon_controls,
Color::dark_grey()};
Color::dark_grey(),
/*vcenter*/ true};
Text text_position{
{0 * 8, 34 * 8, 26 * 8, 2 * 8},
@@ -85,7 +85,6 @@ void TouchTunesView::on_tx_progress(const uint32_t progress, const bool done) {
// transmission events.
void TouchTunesView::start_ew() {
// Radio
transmitter_model.set_target_frequency(433920000);
transmitter_model.set_rf_amp(true);
transmitter_model.set_tx_gain(47);
transmitter_model.enable();
@@ -151,10 +150,7 @@ void TouchTunesView::start_tx(const uint32_t button_index) {
size_t bitstream_length = make_bitstream(fragments);
transmitter_model.set_target_frequency(433920000);
transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
transmitter_model.set_rf_amp(true);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_ook_data(
+4 -2
View File
@@ -25,6 +25,7 @@
#include "ui_transmitter.hpp"
#include "transmitter_model.hpp"
#include "radio_state.hpp"
#include "encoders.hpp"
// The coding in notpike's script is quite complex, using multiple LUTs to form the data sent to the YSO.
// The format is actually very simple if it is rather seen as short and long gaps between pulses (as seen in many OOK remotes).
@@ -115,8 +116,9 @@ class TouchTunesView : public View {
private:
TxRadioState radio_state_{
3500000 /* bandwidth */,
3072000 /* sampling rate */
433920000 /* frequency */,
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
uint32_t scan_button_index{};
+14 -11
View File
@@ -138,17 +138,20 @@ ViewWavView::ViewWavView(
reset_controls();
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".WAV");
open_view->on_changed = [this](std::filesystem::path file_path) {
if (!wav_reader->open(file_path)) {
nav_.display_modal("Error", "Couldn't open file.");
return;
}
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.");
return;
}
load_wav(file_path);
field_pos_seconds.focus();
open_view->on_changed = [this, &nav](std::filesystem::path file_path) {
// Can't show new dialogs in an on_changed handler, so use continuation.
nav.set_on_pop([this, &nav, file_path]() {
if (!wav_reader->open(file_path)) {
nav_.display_modal("Error", "Couldn't open file.");
return;
}
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.");
return;
}
load_wav(file_path);
field_pos_seconds.focus();
});
};
};
+10 -2
View File
@@ -198,8 +198,8 @@ void update_audio_mute() {
namespace input {
void start(int8_t alc_mode) {
audio_codec->microphone_enable(alc_mode); // added user-GUI selection for AK4951, ALC mode parameter.
void start(int8_t alc_mode, bool mic_to_HP_enabled) {
audio_codec->microphone_enable(alc_mode, mic_to_HP_enabled); // added user-GUI selection for AK4951, ALC mode parameter. and the check box "Hear Mic"
i2s::i2s0::rx_start();
}
@@ -208,6 +208,14 @@ void stop() {
audio_codec->microphone_disable();
}
void loopback_mic_to_hp_enable() {
audio_codec->microphone_to_HP_enable();
}
void loopback_mic_to_hp_disable() {
audio_codec->microphone_to_HP_disable();
}
} /* namespace input */
namespace headphone {
+8 -2
View File
@@ -50,9 +50,12 @@ class Codec {
virtual volume_range_t headphone_gain_range() const = 0;
virtual void set_headphone_volume(const volume_t volume) = 0;
virtual void microphone_enable(int8_t alc_mode) = 0; // added user-GUI AK4951 ,selected ALC mode.
virtual void microphone_enable(int8_t alc_mode, bool mic_to_HP_enabled) = 0; // added user-GUI AK4951 ,selected ALC mode.
virtual void microphone_disable() = 0;
virtual void microphone_to_HP_enable() = 0;
virtual void microphone_to_HP_disable() = 0;
virtual size_t reg_count() const = 0;
virtual size_t reg_bits() const = 0;
virtual uint32_t reg_read(const size_t register_number) = 0;
@@ -76,9 +79,12 @@ void update_audio_mute();
namespace input {
void start(int8_t alc_mode); // added parameter user-GUI select AK4951-ALC mode for config mic path,(recording mode in datasheet),
void start(int8_t alc_mode, bool mic_to_HP_enabled); // added parameters -GUI select AK4951-ALC mode for config mic path,(recording mode),and the check box "Hear Mic"
void stop();
void loopback_mic_to_hp_enable();
void loopback_mic_to_hp_disable();
} /* namespace input */
namespace headphone {
+24
View File
@@ -329,6 +329,30 @@ void run_image(const spi_flash::image_tag_t image_tag) {
}
}
void run_prepared_image(const uint32_t m4_code) {
if (baseband_image_running) {
chDbgPanic("BBRunning");
}
creg::m4txevent::clear();
shared_memory.clear_baseband_ready();
m4_init_prepared(m4_code, false);
baseband_image_running = true;
creg::m4txevent::enable();
if constexpr (enforce_core_sync) {
// Wait up to 3 seconds for baseband to start handling events.
auto count = 3'000u;
while (!shared_memory.baseband_ready && --count)
chThdSleepMilliseconds(1);
if (count == 0)
chDbgPanic("Baseband Sync Fail");
}
}
void shutdown() {
if (!baseband_image_running) {
return;
+1
View File
@@ -89,6 +89,7 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
void request_beep();
void run_image(const portapack::spi_flash::image_tag_t image_tag);
void run_prepared_image(const uint32_t m4_code);
void shutdown();
void spectrum_streaming_start();
+117
View File
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* 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 __BINDER_H__
#define __BINDER_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_widget.hpp"
namespace ui {
/* Default no-op binding callback. */
struct NoOp {
NoOp() {}
template <typename T>
void operator()(T) const {}
};
/* The gist of the bind functions is to bind a control
* to a value.
* bind(control, value)
*
* An optional callback can be specified which will be called
* when the control's value is changed.
* bind(control, value, [](auto new_val) { ... });
*
* Some controls need a reference to the NavigationView so
* edit dialogs (like text_prompt) can be launched.
* bind(control, value, nav, [](auto new_val) { ... });
*
* Be careful with lifetime of captured objects. Most things
* are captured by reference so the caller will need to ensure
* adequate lifetime of the referenced instances.
*/
template <typename T, typename Fn = NoOp>
void bind(Checkbox& check, T& value, Fn fn = Fn{}) {
check.set_value(value);
check.on_select = [&value, fn](Checkbox&, bool b) {
value = b;
fn(value);
};
}
template <typename T, typename Fn = NoOp>
void bind(NumberField& field, T& value, Fn fn = Fn{}) {
field.set_value(value);
field.on_change = [&value, fn](int32_t v) {
value = v;
fn(value);
};
}
template <typename T, typename Fn = NoOp>
void bind(OptionsField& field, T& value, Fn fn = Fn{}) {
field.set_by_value(static_cast<int32_t>(value));
field.on_change = [&value, fn](size_t, auto v) {
value = static_cast<T>(v);
fn(value);
};
}
template <typename T, typename Fn = NoOp>
void bind(FrequencyField& field, T& value, NavigationView& nav, Fn fn = Fn{}) {
field.set_value(value);
field.on_change = [&value, fn](rf::Frequency f) {
value = f;
fn(value);
};
field.on_edit = [&field, &value, &nav]() {
auto freq_view = nav.push<FrequencyKeypadView>(value);
freq_view->on_changed = [&field](rf::Frequency f) {
field.set_value(f);
};
};
}
template <typename T, typename Fn = NoOp>
void bind(TextField& field, T& value, NavigationView& nav, Fn fn = Fn{}) {
field.set_text(value);
field.on_change = [&value, fn](TextField& tf) {
value = tf.get_text();
fn(value);
};
// text_prompt needs a mutable working buffer.
// Capture a new string and make the lambda mutable so it can be modified.
field.on_select = [&nav, buf = std::string{}](TextField& tf) mutable {
buf = tf.get_text();
text_prompt(nav, buf, /*max_length*/ 255,
[&tf](std::string& str) {
tf.set_text(str);
});
};
}
} // namespace ui
#endif /*__BINDER_H__*/
+38
View File
@@ -5645,6 +5645,44 @@ static constexpr Bitmap bitmap_icon_shift{
{16, 16},
bitmap_icon_shift_data};
static constexpr uint8_t bitmap_icon_trim_data[] = {
0x10,
0x10,
0x30,
0x12,
0x50,
0x15,
0x50,
0x15,
0x50,
0x15,
0x52,
0x95,
0x56,
0xD5,
0x5F,
0xF5,
0x56,
0xD5,
0x52,
0x95,
0x50,
0x15,
0x50,
0x15,
0x50,
0x15,
0x90,
0x18,
0x10,
0x10,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_trim{
{16, 16},
bitmap_icon_trim_data};
} /* namespace ui */
#endif /*__BITMAP_HPP__*/
+13
View File
@@ -59,6 +59,19 @@ void m4_init(const spi_flash::image_tag_t image_tag, const memory::region_t to,
chDbgPanic("NoImg");
}
void m4_init_prepared(const uint32_t m4_code, const bool full_reset) {
/* M4 core is assumed to be sleeping with interrupts off, so we can mess
* with its address space and RAM without concern.
*/
LPC_CREG->M4MEMMAP = m4_code;
/* Reset M4 core and optionally all peripherals */
LPC_RGU->RESET_CTRL[0] = (full_reset) ? (1 << 1) // PERIPH_RST
: (1 << 13); // M4_RST
return;
}
void m4_request_shutdown() {
baseband::shutdown();
}
+1
View File
@@ -28,6 +28,7 @@
#include "spi_image.hpp"
void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to, const bool full_reset);
void m4_init_prepared(const uint32_t m4_code, const bool full_reset);
void m4_request_shutdown();
void m0_halt();
+7 -4
View File
@@ -57,6 +57,9 @@ uint32_t fault_address{0};
void draw_guru_meditation_header(uint8_t source, const char* hint) {
Painter painter;
// disable scroll in case a waterfall/spectrum display was active (scroll would mess up the fault display)
portapack::display.scroll_disable();
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
Color::red());
@@ -134,6 +137,10 @@ void runtime_error(uint8_t source) {
led.off();
// wait for DFU button release if pressed, so we don't immediately jump into stack dump
while (swizzled_switches() & (1 << (int)Switch::Dfu))
;
while (true) {
volatile size_t n = 1000000U;
while (n--)
@@ -243,10 +250,6 @@ void draw_stack_dump() {
p = &__process_stack_base__;
break;
}
// If SEL button pressed, try writing to file (likely to hang in fault condition but heh)
if (switches & (1 << (int)Switch::Sel))
stack_dump();
}
// clear screen to allow new range to be displayed, if we're not at the end

Some files were not shown because too many files have changed in this diff Show More