mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-07 23:39:02 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3db8a5ac5b | |||
| 266a626acf | |||
| 411e2fb93a | |||
| 6ed83c08e3 | |||
| 9929376acf | |||
| 5135b6404d | |||
| 790669c242 | |||
| 4bb8b1882c | |||
| 56702ca7c2 | |||
| 9be4b9596d | |||
| 37c6b35fb7 | |||
| 5cd7a09797 | |||
| a1856b9bb0 | |||
| 29a36c8658 | |||
| 264b1b38ba | |||
| 65e71508ac | |||
| e5546159c5 | |||
| d7c961ea4f | |||
| ad987f2b5e | |||
| defb934c3f | |||
| 5a850984b9 | |||
| 1185b67b28 | |||
| abb42e8df4 | |||
| 7fdb1af69d | |||
| 78713cc2af | |||
| 951890eaff | |||
| fb00bfac3f | |||
| a6a1483083 | |||
| ef03f020ce | |||
| 070eb4003e |
@@ -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 }}
|
||||
|
||||
@@ -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 @@
|
||||
v1.7.3
|
||||
v1.7.4
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.7.4
|
||||
v1.8.0
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
[](https://www.youtube.com/watch?v=kjFB58Y1TAo)
|
||||
[](https://www.youtube.com/watch?v=alrFbY5vxt4)
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
|
||||
+10
-2
@@ -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.
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -244,8 +244,7 @@ SettingsManager::SettingsManager(
|
||||
: app_name_{app_name},
|
||||
settings_{},
|
||||
bindings_{},
|
||||
loaded_{false},
|
||||
radio_loaded_{false} {
|
||||
loaded_{false} {
|
||||
settings_.mode = mode;
|
||||
settings_.options = options;
|
||||
|
||||
@@ -253,53 +252,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_);
|
||||
}
|
||||
|
||||
@@ -170,8 +170,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 +179,6 @@ class SettingsManager {
|
||||
AppSettings settings_;
|
||||
SettingBindings bindings_;
|
||||
bool loaded_;
|
||||
bool radio_loaded_;
|
||||
};
|
||||
|
||||
} // namespace app_settings
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
@@ -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_????.*",
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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_{
|
||||
|
||||
@@ -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&) {
|
||||
|
||||
@@ -51,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};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -51,6 +51,7 @@ class LGEView : public View {
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
868067000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -50,6 +50,7 @@ class SoundBoardView : public View {
|
||||
|
||||
private:
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -342,6 +342,7 @@ class ADSBRxView : public View {
|
||||
|
||||
private:
|
||||
RxRadioState radio_state_{
|
||||
1090000000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
2000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::SpectrumAnalysis};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -203,7 +203,8 @@ class ADSBTxView : public View {
|
||||
};*/
|
||||
|
||||
TxRadioState radio_state_{
|
||||
10000000 /* bandwidth */,
|
||||
1090000000 /* frequency */,
|
||||
6000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ class APRSTXView : public View {
|
||||
|
||||
private:
|
||||
TxRadioState radio_state_{
|
||||
144390000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
AFSK_TX_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -50,6 +50,7 @@ class BTLERxView : public View {
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
2426000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
@@ -400,7 +401,6 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
add_items({
|
||||
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
|
||||
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
|
||||
//{"Fonts Viewer", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugFontsView>(); }}, // temporarily disabled to conserve ROM space
|
||||
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { stack_dump(); }},
|
||||
{"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>(); }},
|
||||
@@ -410,6 +410,11 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
|
||||
{"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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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,6 +30,7 @@
|
||||
#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"
|
||||
@@ -108,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;
|
||||
}
|
||||
@@ -189,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)) {
|
||||
@@ -325,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();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -563,6 +567,7 @@ FileManagerView::FileManagerView(
|
||||
&button_new_file,
|
||||
&button_open_notepad,
|
||||
&button_rename_timestamp,
|
||||
&button_open_iq_trim,
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
@@ -645,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
|
||||
|
||||
@@ -272,6 +272,12 @@ class FileManagerView : public FileManBaseView {
|
||||
&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()};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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,
|
||||
&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();
|
||||
};
|
||||
|
||||
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));
|
||||
text_max.set(to_string_dec_uint(info_->max_power));
|
||||
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());
|
||||
progress_ui.clear();
|
||||
|
||||
if (!trimmed)
|
||||
nav_.display_modal("Error", "Trimming failed.");
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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()},
|
||||
};
|
||||
|
||||
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, 10 * 8, 1 * 16},
|
||||
"0"};
|
||||
|
||||
NumberField field_cutoff{
|
||||
{9 * 8, 10 * 16},
|
||||
3,
|
||||
{1, 100},
|
||||
1,
|
||||
' '};
|
||||
|
||||
Button button_trim{
|
||||
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
|
||||
"Trim"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__UI_IQ_TRIM_H__*/
|
||||
@@ -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());
|
||||
|
||||
@@ -101,6 +101,7 @@ class JammerView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
3500000 /* bandwidth */,
|
||||
3072000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -44,6 +44,7 @@ class KeyfobView : public View {
|
||||
NavigationView& nav_;
|
||||
|
||||
TxRadioState radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
OOK_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -82,6 +82,7 @@ class LCRView : public View {
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
AFSK_TX_SAMPLERATE /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -82,6 +82,7 @@ class MicTXView : public View {
|
||||
|
||||
RxRadioState rx_radio_state_{};
|
||||
TxRadioState tx_radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
sampling_rate /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -70,6 +70,7 @@ class MorseView : public View {
|
||||
uint32_t time_units{0};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -50,6 +50,7 @@ class NRFRxView : public View {
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
2480000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ class NumbersStationView : public View {
|
||||
NavigationView& nav_;
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ class NuoptixView : public View {
|
||||
};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -60,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 = 2'500'000;
|
||||
|
||||
struct playlist_entry {
|
||||
std::filesystem::path path{};
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
@@ -142,6 +142,7 @@ class RDSView : public View {
|
||||
RDS_flags rds_flags{};
|
||||
|
||||
TxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,
|
||||
' ',
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "ui_remote.hpp"
|
||||
|
||||
#include "binder.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "io_convert.hpp"
|
||||
@@ -239,19 +240,9 @@ RemoteEntryEditView::RemoteEntryEditView(
|
||||
&button_done,
|
||||
});
|
||||
|
||||
// TODO: It's time to make field bindings and clean this mess up.
|
||||
field_name.on_change = [this](TextField& tf) {
|
||||
entry_.name = tf.get_text();
|
||||
button_preview.set_text(entry_.name);
|
||||
};
|
||||
field_name.on_select = [this, &nav](TextField& tf) {
|
||||
temp_buffer_ = tf.get_text();
|
||||
text_prompt(nav, temp_buffer_, text_edit_max,
|
||||
[this, &tf](std::string& str) {
|
||||
tf.set_text(str);
|
||||
});
|
||||
};
|
||||
field_name.set_text(entry_.name);
|
||||
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*");
|
||||
@@ -262,31 +253,16 @@ RemoteEntryEditView::RemoteEntryEditView(
|
||||
};
|
||||
};
|
||||
|
||||
field_freq.on_edit = [this, &nav]() {
|
||||
auto freq_view = nav.push<FrequencyKeypadView>(entry_.metadata.center_frequency);
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
entry_.metadata.center_frequency = f;
|
||||
field_freq.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_icon_index.on_change = [this](int32_t v) {
|
||||
entry_.icon = v;
|
||||
bind(field_freq, entry_.metadata.center_frequency, nav);
|
||||
bind(field_icon_index, entry_.icon, [this](auto v) {
|
||||
button_preview.set_bitmap(RemoteIcons::get(v));
|
||||
};
|
||||
field_icon_index.set_value(entry.icon);
|
||||
|
||||
field_fg_color_index.on_change = [this](int32_t v) {
|
||||
entry_.fg_color = v;
|
||||
});
|
||||
bind(field_fg_color_index, entry_.fg_color, [this](auto v) {
|
||||
button_preview.set_color(RemoteColors::get(v));
|
||||
};
|
||||
field_fg_color_index.set_value(entry_.fg_color);
|
||||
|
||||
field_bg_color_index.on_change = [this](int32_t v) {
|
||||
entry_.bg_color = v;
|
||||
});
|
||||
bind(field_bg_color_index, entry_.bg_color, [this](auto) {
|
||||
button_preview.set_dirty();
|
||||
};
|
||||
field_bg_color_index.set_value(entry_.bg_color);
|
||||
});
|
||||
|
||||
button_delete.on_select = [this, &nav]() {
|
||||
nav.display_modal(
|
||||
@@ -485,6 +461,7 @@ void RemoteView::edit_button(RemoteButton& btn) {
|
||||
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) {
|
||||
@@ -555,7 +532,6 @@ void RemoteView::open_remote() {
|
||||
save_remote();
|
||||
load_remote(std::move(path));
|
||||
refresh_ui();
|
||||
;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,6 @@ class RemoteEntryEditView : public View {
|
||||
|
||||
private:
|
||||
RemoteEntryModel& entry_;
|
||||
std::string temp_buffer_{};
|
||||
|
||||
void refresh_ui();
|
||||
void load_path(std::filesystem::path&& path);
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
@@ -654,7 +626,6 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetAppSettingsView>(); }},
|
||||
{"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>(); }},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -62,6 +62,7 @@ class TestView : public View {
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
0 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2457600 * 2 /* sampling rate */
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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{};
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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__*/
|
||||
@@ -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__*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_afsk_rx.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::afsk_rx {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<AFSKRxView>();
|
||||
}
|
||||
} // namespace ui::external_app::afsk_rx
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_afsk_rx.application_information"), used)) application_information_t _application_information_afsk_rx = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::afsk_rx::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "AFSK",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0x04,
|
||||
0x20,
|
||||
0x02,
|
||||
0x40,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xAB,
|
||||
0xDF,
|
||||
0xAB,
|
||||
0xDF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::RX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
Vendored
+5
-4
@@ -32,20 +32,21 @@
|
||||
|
||||
using namespace portapack;
|
||||
using namespace modems;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::afsk_rx {
|
||||
|
||||
void AFSKLogger::log_raw_data(const std::string& data) {
|
||||
log_file.write_entry(data);
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
void AFSKRxView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
AFSKRxView::AFSKRxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_afsk_rx);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
@@ -147,4 +148,4 @@ AFSKRxView::~AFSKRxView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::afsk_rx
|
||||
Vendored
+5
-3
@@ -33,6 +33,10 @@
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::afsk_rx {
|
||||
|
||||
class AFSKLogger {
|
||||
public:
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
@@ -45,8 +49,6 @@ class AFSKLogger {
|
||||
LogFile log_file{};
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
class AFSKRxView : public View {
|
||||
public:
|
||||
AFSKRxView(NavigationView& nav);
|
||||
@@ -116,6 +118,6 @@ class AFSKRxView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} // namespace ui::external_app::afsk_rx
|
||||
|
||||
#endif /*__UI_AFSK_RX_H__*/
|
||||
+2072
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user