Compare commits

...

104 Commits

Author SHA1 Message Date
gullradriel b306a0d490 Revert "Update Map view labels to match marine navigation standards (#2951)" (#2955)
This reverts commit c6130c6e8d.
2026-01-31 21:15:12 +01:00
Herbenderbler c6130c6e8d Update Map view labels to match marine navigation standards (#2951)
Replace Alt: with CoG: and Spd: with SoG: in the Map view to better align with standard marine navigation terminology and the AIS Rx screen.

- Alt (Altitude) → CoG (Course over Ground)
- Spd (Speed) → SoG (Speed over Ground)

Rationale: AIS is primarily used for marine navigation where altitude is typically zero (sea level), while Course over Ground is essential navigation information. This change provides consistency with the AIS Rx screen which already displays CoG and SoG.

Modified files:
- firmware/application/ui/ui_geomap.hpp
- firmware/standalone/common/ui/ui_geomap.hpp
2026-01-31 20:53:58 +01:00
Herbenderbler 97f2ca42f1 Fix aviation 8.33kHz step size to reduce frequency drift (#2950)
Update aviation radio step size from 8330 Hz to 8333 Hz in freqman_steps
and freqman_steps_short arrays. This better represents the actual 8⅓ kHz
(25000/3 Hz) aviation channel spacing and significantly reduces frequency
drift during dial tuning.
With this fix, stepping 24 times from 123.000 MHz now correctly reaches
123.200 MHz instead of 123.199 MHz.
2026-01-31 19:35:14 +01:00
Pezsma e2b642ae25 New Morse TX app (#2948)
* morse tx
* global button repeat function improvement
2026-01-30 17:18:13 +01:00
gullradriel 106d9b1207 updated submodule (#2947) 2026-01-30 10:14:08 +01:00
gullradriel c8ae419e0b updated submodule (#2946) 2026-01-30 00:21:36 +01:00
Totoo 2319dcd0d6 Aprs tx gps added, and save config added, path added (#2937) 2026-01-25 10:01:33 +01:00
lifegame1lu111 44d9ce00cd Holtek HT6P20B support for SubGhzD (#2941) 2026-01-25 09:41:06 +01:00
Pezsma 5cc5f8faa6 Morserx added ssb modes (#2934)
* USB receive good
* LSB receive good
* all mode receive good, log is dead
* frequency measurement good, decode dead
* total killed
* szutyok, gerjed
* frequenty meas only.
* ui ok
* Add frequency measurement handling and update Morse processing logic
* fix ssb speed
2026-01-22 18:49:21 +01:00
gullradriel 3a0ca480b0 conform to clang spec (#2925)
* conform to clang spec
* proper find call
* Update format-code.sh
2026-01-22 15:26:31 +01:00
Totoo 7a2b432bfa Dyn flash size (#2933) 2026-01-22 15:24:42 +01:00
zxkmm 86d7b345d0 waterfall color designer app (#2625) 2026-01-19 21:46:25 +01:00
Pezsma 877ede5f86 Added morse receiver app. (#2923)
* Adder morse receiver app. Works with cw and fm mode. Adaptive speed learning. Logging.
2026-01-16 09:57:45 +01:00
gullradriel 19eb1c40ab fix NumberField wrapping when steps is not '1' and minimum can be negative (#2920) 2026-01-11 22:49:18 +01:00
gullradriel 9c03a18893 Looking glass marker no encoder (#2919)
* enable repeat mode for select button too
* added marker + and marker - buttons, removed non effective iqcalc tuning
2026-01-10 20:19:10 +01:00
gullradriel cd504eb4a7 Udpdate hackrf submodule (#2906)
* add missing parameter to usb endpoint init
* make firmware compile with latest hackrf
* updated submodule
* format code
2026-01-10 16:19:53 +01:00
Totoo dc976c7f38 make sd over usb external (#2918) 2026-01-10 15:10:23 +01:00
Totoo 266d90a600 Updated subcar processors (#2913) 2026-01-10 14:47:33 +01:00
Totoo 2884bb9ab7 make siggen ext (#2917) 2026-01-10 21:25:41 +08:00
Totoo 133b2d2065 fix for #2903 (#2914) 2026-01-10 20:25:30 +08:00
Totoo 2f2671f76c Some tweaks to support some usecases without encoder, also maximize the NewButton's text size (#2915) 2026-01-10 20:21:57 +08:00
Totoo f5c2c78733 fix looking glass marker on portarf (#2912) 2026-01-09 20:32:31 +00:00
Harin Lee e4ab7227fc Fix typos of 'transceiver' (#2910) 2026-01-08 10:34:05 +01:00
Harin Lee 93799bde6d New approach to support RX-only mode more cleanly (#2908)
* Add enabled checks in ReceiverModel and TransmitterModel

* Move TX limit logic to TransmitterModel from radio API

* Add TX disable functionality and UI
2026-01-07 12:29:56 +08:00
RML 3d979a613a remove github pages CNAME (#2905) 2026-01-04 09:25:45 +01:00
Harin Lee ee97b51eb3 Add some features to support RX-only mode (#2904)
* Remove duplicate LED control code
* Add TX limit settings and UI
* Fix App Manager to handle non-application menu items
2026-01-03 22:10:34 +01:00
gullradriel 70ac1a03a1 Make stable release (#2895)
* updated submodule
* updated versions
2025-12-21 11:39:47 +01:00
Totoo 2261e53981 fix bit counts (#2894) 2025-12-21 11:38:35 +01:00
Totoo f86d3e51f1 Subcar (#2893) 2025-12-21 11:21:55 +01:00
StarVore Labs f71f19e719 Addition of SSTV RX application (#2888) 2025-12-17 13:14:19 +01:00
RocketGod c53adfc765 Refactor FLEX RX UI to use console for messages (#2890) 2025-12-14 10:08:39 -08:00
gullradriel 7e8ad83537 Update clang format to 18 (#2891)
* move to clang-format-18
* clang-format-18 indendation
2025-12-14 11:26:21 +01:00
RocketGod 472571b1ed Refactor FLEX RX UI to add color cycling and message log (#2885)
Replaces the console with a scrollable menu view for displaying FLEX messages, adds support for cycling text colors, and implements message logging with line wrapping and persistence. Also refactors frequency handling for persistence and updates UI element layout and initialization.
2025-12-12 19:40:15 +01:00
Tim Elfelt 106e56abc3 FLEX pager app (#2883)
* Add FLEX pager support

- Introduced a new FLEX configuration function in baseband_api.
- Added FLEX application view and associated UI elements.
- Implemented FLEX processing logic in proc_flex, including demodulation and message handling.
- Updated CMakeLists to include new FLEX source files and headers.
- Enhanced message system to support FLEX-specific messages and statistics.

This commit lays the groundwork for FLEX pager functionality, allowing for the reception and processing of FLEX messages.

* Fixed baseband and moved app to external with some other fixes.

* Format code

---------

Co-authored-by: RocketGod <57732082+RocketGod-git@users.noreply.github.com>
2025-12-12 17:43:51 +01:00
zxkmm 4129d57c09 fix dead link (#2882)
* fix dead link

* submodule
2025-12-06 07:34:10 -08:00
Totoo caac5e1041 Sonde + map (#2879)
* Update geomap view position on GPS data reception

* Refactor timestamp and temperature/humidity display formatting in SondeView

* Implement battery voltage reading for Meteomodem M20

* Enhance GPS data validation and update OSM zoom handling

- Introduced a new method to validate GPS data in the Packet structure.
- Updated the SondeView to use the new GPS validation method.
- Modified GeoMap to improve handling of OSM zoom levels and ensure consistent usage of real zoom values.
- Adjusted battery voltage calculation for Meteomodem M20 to ensure correct scaling.

* Add serial number extraction for Meteomodem M20 support

* Add support for Meteomodem M20 temperature and humidity readings

* Update log file naming to include timestamp in SondeView

* Add pressure reading support for Meteomodem M20 and update UI

* Update SondeView UI layout and enhance Meteomodem M20 packet handling

* Add vertical speed calculation and display to SondeView

* Fix set_fsk function parameter type for samplesPerSymbol
2025-11-26 19:13:21 +01:00
Totoo 6b02ba6e5d Faster osm (#2874)
Much faster OSM map handler
2025-11-21 09:30:01 +01:00
Totoo c01597baf2 Pocsag manual baud option (#2870)
* baseband part of manual baud control of pocsag

* added manual pocsag baud to ui, to fix #2546

* fix adult toys settings filename
2025-11-18 14:05:27 +01:00
Totoo 43a5163b77 prevent sonde pos to change to 0 when the frame is invalid. fixes #2862 (#2869) 2025-11-18 19:07:40 +08:00
jLynx c46cc431c9 Revert "resolve conflicts merge" (#2860)
This reverts commit 344aa0c741.
2025-11-08 10:21:33 +13:00
jLynx a4d23768c1 Update version.txt (#2858) 2025-11-08 10:13:35 +13:00
gullradriel 344aa0c741 resolve conflicts merge 2025-11-07 21:36:41 +01:00
gullradriel fd7637187a updated version number (#2855)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-11-07 20:52:28 +01:00
Totoo 674b865ec0 Refactor OnTouchEvent functions to return boolean values for better event handling (#2854) 2025-11-05 20:53:23 +01:00
Totoo 00c9c6b40e Fix shift key position (#2850) 2025-10-31 18:25:23 +01:00
RocketGod 113cbc42c9 Add sound effects (#2849)
* Add sound effects

Adding sound effects and volume adjuster in start menu. Sounds are a little clicky but I tinkered a long time and couldn't solve that. Maybe someone else wants to tinker a bit with me.

* Format code
2025-10-30 07:38:45 +01:00
RocketGod 454b8776b6 Refactor gfxEQView UI layout for consistency (#2848)
* Refactor gfxEQView UI layout for consistency

Updated control and graph positioning in gfxEQView to use a standard layout approach, aligning with other applications. Screen dimensions are now used for dynamic placement, and hardcoded values have been replaced for improved maintainability and consistency.

Basically, centered for smaller and larger screens with other visual improvements.

* Fixed screen to Macros - looks good now

Better methods for dynamic screen size
2025-10-30 07:33:38 +01:00
Matej Sochan efdf043d62 fix typo: wps -> wpm in morse_tx (#2847) 2025-10-28 20:55:21 +01:00
gullradriel 0f9d9e589d Revert "Additional debounce control parameters for rotary encoder settings (f…" (#2846)
This reverts commit bf18851b6b.
2025-10-28 22:34:14 +13:00
Totoo 3b57672dda sht4x support,, typo fix (#2843) 2025-10-28 10:14:11 +08:00
Totoo b739dd883c Free up some fw space (#2844) 2025-10-28 08:10:09 +08:00
rmckay bf18851b6b Additional debounce control parameters for rotary encoder settings (for bad/noisy encoders) (#2841)
* Added debounce control options for rotary encoder settings

* ran format-code.sh to adjust whitespace

---------

Co-authored-by: Robert McKay <robert.mckay@ubermorgen.land>
2025-10-27 23:56:27 +01:00
Pezsma 81afec9ea4 Added Adult toy controller external app (#2840) 2025-10-23 13:09:48 +02:00
Totoo 60a5222058 Pacman update (#2837) 2025-10-21 07:37:26 +02:00
Totoo 7041d507ca gracefully handle i2c error (#2838) 2025-10-21 07:37:14 +02:00
Totoo d3373734e9 battleship resizes based on screen size (#2836) 2025-10-18 17:00:50 +02:00
Totoo 200831ad67 fix some bugs due to framesync not fired on stealth mode (#2834) 2025-10-18 13:13:51 +02:00
gullradriel dafe3b7641 update hackrf submodule (#2832) 2025-10-17 19:28:18 +02:00
gullradriel 63634407b6 fix ctrl click link (#2833) 2025-10-17 17:09:57 +00:00
Totoo 9f06ccf9ae Support some space chars in serial commands (fs) (#2828) 2025-10-17 17:05:27 +02:00
Totoo 6782e12a15 Fix morse tx cw+loop (#2827) 2025-10-17 17:03:34 +02:00
Totoo cbbd8103d3 Touch for MenuView and Freqlist (#2829)
* freqman add touch support. solves #1919

* menuview touch support
2025-10-17 21:59:35 +08:00
Pezsma e88ab44f49 Modify Morse code table (#2825)
Fixed morse code table
2025-10-17 10:32:01 +02:00
Pezsma 545dead1ca Added new game: Morse trainer (#2820)
* Added new game: Morse trainer

* code format error fix, and ui alignment
2025-10-16 13:01:28 +02:00
Totoo e3b2a65b39 Oregon fixes (#2822) 2025-10-16 13:00:40 +02:00
Totoo 2f4d222403 New memory address for i2c dev loaded apps (#2817)
* New memory address for i2c dev loaded apps

* better download handling on error

* new hackrf version (added PortapackRf support)

* lock i2cdev while loading apps
2025-10-14 09:45:52 +02:00
Totoo 54d99945f2 fix touch calib on prf (#2816) 2025-10-14 09:03:28 +08:00
RocketGod 65a3c797c6 Blackjack - Dynamic screen for PortaRF (#2814)
Blackjack - Dynamic screen for PortaRF
2025-10-10 22:48:26 +02:00
Totoo 6a81a981aa Fix standalont touch_release event (#2813) 2025-10-10 22:47:17 +02:00
RocketGod 193ac73b34 Doom UI improvements for dynamic screen (#2812)
* Doom dynamic screen and other improvements

Doom dynamic screen and other improvements

* Res divider fix for big screens

* Format code
2025-10-09 22:54:54 +02:00
RocketGod 477d035dce Dino Game Dynamic Screen for PortaRF plus sprite animation improvements (#2811)
* Dino Game Dynamic Screen for PortaRF plus sprite animation improvements

Dino Game Dynamic Screen for PortaRF plus sprite animation improvements

* Format code per usual
2025-10-09 22:31:26 +02:00
RocketGod 1d8f53b49e Space invaders UI improvements for all device screens including new PortaRF (#2810)
* Dynamic screen for PortaRF

Dynamic screen size for new PortaRF device.

* Format code

* Space Invaders improvements for all devices including new PortaRF

Space Invaders improvements for all devices including new PortaRF
2025-10-09 22:30:30 +02:00
Totoo db324a5225 PortaRF battery fix (#2809) 2025-10-09 22:29:53 +02:00
Totoo b2331bf818 clarify battery settings (#2805) 2025-10-06 10:29:17 +02:00
Totoo 4b18ac42e9 full blue batt when charging (#2804) 2025-10-05 17:04:30 +02:00
RocketGod 41dd0c4674 Dynamic screen for PortaRF Tetris (#2803)
* Dynamic screen for PortaRF
* Format code
2025-10-04 19:19:54 +02:00
Totoo 371b6b5079 Multi screen support, with dyn alignment (#2801) 2025-10-03 19:10:10 +02:00
RocketGod 23cabb8b8a Make dynamic screen for PortaRF (#2800) 2025-10-02 11:31:33 -07:00
Totoo b3edaf3cd0 AIS name fix (#2793)
* AIS name fix
2025-09-17 22:40:46 +02:00
Totoo 9afd3a9dca Standalone imp (#2791)
* Moved ui elements to common folder in standalone, and add screen parameters to the api v4

* pass it as a pointer, so if standalone app supports it can change
2025-09-16 20:29:05 +02:00
Totoo d80f41e9d7 Beep on search find something.
Beep on search find something. Solves #2643
2025-09-14 07:22:48 +00:00
Totoo 46d87d95ae Set MIC app's freq step a bit lower to 12.5k. Solves #2728 (#2788) 2025-09-13 12:38:29 +02:00
Totoo 7626dff1ef Add blue bar (Channel) to sonde app. 2025-09-13 12:37:34 +02:00
Totoo f101680521 fixes osm marker display bug #2783 (#2786) 2025-09-13 12:37:01 +02:00
Totoo 325f295ee8 Log function for search app (#2782) 2025-09-07 22:44:04 +08:00
Totoo b15bb59678 Standalone app api v3 (#2772)
Added file io, and updated some ui elements.
Also added Digital Rain standalone app for an example.
2025-09-01 11:50:46 +02:00
Totoo 776c9bc7c9 Fix jammer ranges, clean code (#2776) 2025-08-30 22:22:14 +02:00
Totoo b9ebefc7c7 AIS map speed fix (#2777) 2025-08-30 15:01:29 +02:00
Netro 70ace4be8f BLE RX Lag Improvements (#2770)
* Better handling on filtering to improve lag.
2025-08-27 15:46:53 +02:00
Totoo fe826d5d98 Fix bmp16bit with nocomp header (#2769) 2025-08-27 19:07:08 +08:00
Totoo 4dda7cfaa5 Openstreetmap support (#2765) 2025-08-27 06:37:57 +00:00
zxkmm e6d4081c06 Add game 2048 (#2767)
* _

* rename my name

* fix warnings

* fix warnings
2025-08-26 07:26:53 -07:00
zxkmm 4f738adc70 rename my names in comments (#2768)
* rename my name

* remove wrong line
2025-08-26 19:56:39 +08:00
Totoo d516191ccb Update external.cmake (#2764)
Disable acars ext app, since not working
2025-08-22 10:44:55 +02:00
gullradriel 29bdc539a3 Fix BLE TX warning, regenerate bitmap.hpp (#2762)
* fix 'progress' variable warning, added a TODO
* up to date bitmap.hpp file, soundboard icon
2025-08-19 07:19:46 +02:00
Arne Luehrs 29bba4d0ea Epirb 406 v2 (#2761)
* Adding channel selection to EPIRB receiver

* UI enhancement, Packet error check and color display of error

* code formating
2025-08-18 09:09:37 +02:00
zxkmm a6f886ad0a - (#2759) 2025-08-16 10:50:52 +02:00
Totoo 2d2d9223f2 better rs41 (#2757) 2025-08-15 16:24:07 +02:00
zxkmm 6e33007cb6 move soundbrd app external (#2756) 2025-08-15 13:31:18 +02:00
zxkmm 3f7b59f27e frequency keypad opt (#2755)
* init

* revert changes that caused by batch replace

* revert changes that caused by batch replace

* revert changes that caused by batch replace

* revert submodu;e
2025-08-13 14:25:21 +02:00
Arne Luehrs 375d1ad54e Implementation of EPIRB receiver (#2754)
* Implementation of EPIRB receiver
* Baseband processing of EPIRB signal
* UI to ddecode and display EPIRB message with display on a map
* External application
* External proc element
* Delete CLAUDE.md
2025-08-13 14:24:18 +02:00
Netro 6b05878532 BLE RX/TX Changes (#2752)
* Work on BLE Rx Tx improvements.
* Working on compile size.
* cleanup
* Formatting
* Fixes
* More Improvements + Custom Parsing for Tags
* Moving ERT to external apps.
* Fix Icon.
2025-08-11 07:42:58 +02:00
RocketGod 3983749f11 Update ui_jammer.cpp (#2747)
Update warning to reflect actual range limit of bandwidth.
2025-07-30 20:08:00 +02:00
Totoo 28e2f770e5 fix miss (#2738) 2025-07-12 13:33:22 +02:00
451 changed files with 34732 additions and 4118 deletions
+6 -7
View File
@@ -1,14 +1,13 @@
---
BasedOnStyle: Chromium
IndentWidth: '4'
SortIncludes: 'false'
AllowAllArgumentsOnNextLine: 'true'
IndentWidth: 4
SortIncludes: false
AllowAllArgumentsOnNextLine: true
ColumnLimit: 0
Cpp11BracedListStyle: 'true'
AllowShortLoopsOnASingleLine: 'true'
AllowShortIfStatementsOnASingleLine: 'true'
Cpp11BracedListStyle: true
AllowShortLoopsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: 1
...
+1
View File
@@ -19,5 +19,6 @@ jobs:
- name: clang-format Check
uses: jidicula/clang-format-action@v4.11.0
with:
clang-format-version: '18'
check-path: ${{ matrix.path }}
fallback-style: Chromium
+1 -1
View File
@@ -1 +1 @@
v2.1.0
v2.3.1
+1 -1
View File
@@ -1 +1 @@
v2.2.0
v2.3.2
+2
View File
@@ -62,6 +62,7 @@ cmake-build-debug/
.vscode
.idea
*.swp
.claude
# VSCodium extensions
.history
@@ -82,3 +83,4 @@ venv/
# generated bitmap arr file
# TODO: generate bitmap during build, since we use python during build anyway, lemme know if this is a bad idea @zxkmm
/firmware/tools/bitmap.hpp
firmware/flashsize.h
+21
View File
@@ -27,6 +27,27 @@ set(CMAKE_COLOR_MAKEFILE ON)
add_compile_options(-fdiagnostics-color=always)
#Flash size related options
#This is the size of the flash memory we SUPPORT. Don't worry, the internl flash app has harder limits based on device type.
if(NOT DEFINED FLASH_MB_SIZE)
set(FLASH_MB_SIZE 2) # Default to 2MB if not provided. For PortaRf this should be 2MB passed to cmake with -DFLASH_MB_SIZE=2
endif()
message("Configuring for FLASH_MB_SIZE=${FLASH_MB_SIZE}MB")
#This is the flash memory size we build. This can be less, so the FW size will be less, so can be flashed with older utils.
if(NOT DEFINED FLASH_MB_LIMIT_SIZE)
set(FLASH_MB_LIMIT_SIZE 1) #TODO: should be ${FLASH_MB_SIZE} remove once we got a stable build for all devices we support with bigger than 1 mb flash
endif()
message("Configuring for FLASH_MB_LIMIT_SIZE=${FLASH_MB_LIMIT_SIZE}MB")
if(FLASH_MB_SIZE LESS FLASH_MB_LIMIT_SIZE)
message(FATAL_ERROR "Error: The supported flash size is lower than the generated flash file. Build halted.")
endif()
math(EXPR FLASH_BYTES_SIZE "${FLASH_MB_SIZE} * 1024 * 1024")
math(EXPR FLASH_BYTES_LIMIT_SIZE "${FLASH_MB_LIMIT_SIZE} * 1024 * 1024")
#generate h files
configure_file(flashsize.h.in ${CMAKE_CURRENT_SOURCE_DIR}/firmware/flashsize.h)
option(USE_CCACHE "Enable ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary" OFF)
project(portapack-h1)
-1
View File
@@ -1 +0,0 @@
portapack.ried.cl
+1 -1
View File
@@ -56,7 +56,7 @@ add_subdirectory(test)
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
add_custom_command(
OUTPUT ${FIRMWARE_FILENAME}
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME}
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME} ${FLASH_BYTES_LIMIT_SIZE}
DEPENDS baseband application ${MAKE_SPI_IMAGE}
${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
VERBATIM
+3 -9
View File
@@ -192,7 +192,6 @@ set(CPPSRC
core_control.cpp
database.cpp
gradient.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
file.cpp
@@ -261,7 +260,6 @@ set(CPPSRC
ui/ui_receiver.cpp
ui/ui_rssi.cpp
ui/ui_freqlist.cpp
ui/ui_tv.cpp
ui/ui_spectrum.cpp
ui/ui_tabview.cpp
ui/ui_textentry.cpp
@@ -270,19 +268,15 @@ set(CPPSRC
ui/ui_bmpview.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/ble_comm_app.cpp
apps/ble_rx_app.cpp
apps/ble_tx_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/pocsag_app.cpp
apps/soundboard_app.cpp
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
apps/ui_aprs_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_battinfo.cpp
apps/ui_bht_tx.cpp
apps/ui_bmp_file_viewer.cpp
apps/ui_btle_rx.cpp
apps/ui_debug.cpp
@@ -302,10 +296,8 @@ set(CPPSRC
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
apps/ui_sd_over_usb.cpp
apps/ui_search.cpp
apps/ui_settings.cpp
apps/ui_siggen.cpp
apps/ui_sonde.cpp
apps/ui_ss_viewer.cpp
apps/ui_standalone_view.cpp
@@ -317,7 +309,6 @@ set(CPPSRC
apps/ui_weatherstation.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
protocols/dcs.cpp
protocols/encoders.cpp
protocols/modems.cpp
@@ -476,6 +467,9 @@ add_custom_command(
)
add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
target_link_options(${PROJECT_NAME}.elf PRIVATE
"LINKER:--defsym,LD_FLASH_SIZE=${FLASH_BYTES_LIMIT_SIZE}"
)
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
add_definitions(${DEFS})
include_directories(. ${INCDIR})
+8
View File
@@ -68,6 +68,9 @@ void BoundSetting::parse(std::string_view value) {
as<bool>() = (parsed != 0);
break;
}
case SettingType::Float:
as<float>() = atof(value.data());
break;
};
}
@@ -101,6 +104,11 @@ void BoundSetting::write(File& file) const {
case SettingType::Bool:
file.write(as<bool>() ? "1" : "0", 1);
break;
case SettingType::Float: {
auto float_str = to_string_decimal(as<float>(), 6);
file.write(float_str.c_str(), float_str.length());
}
}
file.write("\r\n", 2);
+4
View File
@@ -52,6 +52,7 @@ class BoundSetting {
U8,
String,
Bool,
Float
};
public:
@@ -73,6 +74,9 @@ class BoundSetting {
BoundSetting(std::string_view name, bool* target)
: name_{name}, target_{target}, type_{SettingType::Bool} {}
BoundSetting(std::string_view name, float* target)
: name_{name}, target_{target}, type_{SettingType::Float} {}
std::string_view name() const { return name_; }
void parse(std::string_view value);
void write(File& file) const;
+14 -11
View File
@@ -223,9 +223,9 @@ void AISRecentEntry::update(const ais::Packet& packet) {
break;
case 5:
call_sign = packet.text(70, 7);
name = packet.text(112, 20);
destination = packet.text(302, 20);
call_sign = trim(packet.text(70, 7));
name = trim(packet.text(112, 20));
destination = trim(packet.text(302, 20));
break;
case 18:
@@ -238,7 +238,7 @@ void AISRecentEntry::update(const ais::Packet& packet) {
break;
case 21:
name = packet.text(43, 20);
name = trim(packet.text(43, 20));
last_position.timestamp = packet.received_at();
last_position.latitude = packet.latitude(192);
last_position.longitude = packet.longitude(164);
@@ -247,11 +247,11 @@ void AISRecentEntry::update(const ais::Packet& packet) {
case 24:
switch (packet.read(38, 2)) {
case 0:
name = packet.text(40, 20);
name = trim(packet.text(40, 20));
break;
case 1:
call_sign = packet.text(90, 7);
call_sign = trim(packet.text(90, 7));
break;
default:
@@ -272,14 +272,14 @@ void RecentEntriesTable<AISRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns&) {
std::string line = ais::format::mmsi(entry.mmsi) + " ";
if (!entry.name.empty()) {
line += ais::format::text(entry.name);
} else {
line += ais::format::text(entry.call_sign);
}
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
@@ -324,7 +324,7 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
if (send_updates)
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground);
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground / 10);
}
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
@@ -456,7 +456,10 @@ void AISAppView::on_tick_second() {
++timer_seconds;
if (timer_seconds % 10 == 0) {
if (recent_entry_detail_view.hidden()) return;
recent_entry_detail_view.update_map_markers(recent);
if (got_new_packet) {
got_new_packet = false;
recent_entry_detail_view.update_map_markers(recent);
}
}
}
@@ -475,7 +478,7 @@ void AISAppView::on_packet(const ais::Packet& packet) {
if (logger) {
logger->on_packet(packet);
}
got_new_packet = true;
auto& entry = ::on_packet(recent, packet.source_id());
entry.update(packet);
recent_entries_view.set_dirty();
+14 -13
View File
@@ -137,10 +137,10 @@ class AISRecentEntryDetailView : public View {
AISRecentEntry entry_{};
Button button_done{
{125, 224, 96, 24},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(6), UI_POS_Y(14), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Done"};
Button button_see_map{
{19, 224, 96, 24},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y(14), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"See on map"};
GeoMapView* geomap_view{nullptr};
bool send_updates{false};
@@ -159,7 +159,7 @@ class AISAppView : public View {
~AISAppView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
@@ -179,9 +179,9 @@ class AISAppView : public View {
AISRecentEntries recent{};
std::unique_ptr<AISLogger> logger{};
const RecentEntriesColumns columns{{
RecentEntriesColumns columns{{
{"MMSI", 9},
{"Name/Call", 20},
{"Name/Call", 0},
}};
AISRecentEntriesView recent_entries_view{columns, recent};
AISRecentEntryDetailView recent_entry_detail_view{nav_};
@@ -189,11 +189,11 @@ class AISAppView : public View {
static constexpr auto header_height = 1 * 16;
Text label_channel{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), 2 * 8, 1 * 16},
"Ch"};
OptionsField options_channel{
{3 * 8, 0 * 16},
{3 * 8, UI_POS_Y(0)},
3,
{
{"87B", 161975000},
@@ -201,26 +201,27 @@ class AISAppView : public View {
}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(23), 4},
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(23), 4},
};
SignalToken signal_token_tick_second{};
uint8_t timer_seconds = 0;
bool got_new_packet{false}; // got any new packet since latest screen update?
MessageHandlerRegistration message_handler_packet{
Message::ID::AISPacket,
+32 -32
View File
@@ -43,19 +43,19 @@ class AMOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
6, // Max option length
{
// Using common messages from freqman_ui.cpp
}};
OptionsField zoom_config{
{23 * 8, 0 * 16},
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
7,
{{"ZOOM x1", 0},
{"ZOOM x2", 6}} // offset index AM modes array FIR filters.
@@ -68,19 +68,19 @@ class AMFMAptOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
17, // Max option length chars "USB+FM(Wefax Apt)"
{
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
}};
OptionsField zoom_config{
{23 * 8, 0 * 16},
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
7,
{{"ZOOM x1", 0},
{"ZOOM x2", 6}} // offset index array filters.
@@ -93,21 +93,21 @@ class NBFMOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
3, // Max option length
{
// Using common messages from freqman_ui.cpp
}};
Text text_squelch{
{7 * 8, 0 * 16, 8 * 8, 1 * 16},
{UI_POS_X(7), UI_POS_Y(0), UI_POS_WIDTH(8), UI_POS_HEIGHT(1)},
"SQ /99"};
NumberField field_squelch{
{10 * 8, 0 * 16},
{UI_POS_X(10), UI_POS_Y(0)},
2,
{0, 99},
1,
@@ -121,11 +121,11 @@ class WFMOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
4, // Max option length
{
// Using common messages from freqman_ui.cpp
@@ -138,11 +138,11 @@ class WFMAMAptOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
16, // Max option char length "80k-NOAA Apt LPF" , example.
{
// Using common messages from freqman_ui.cpp
@@ -155,11 +155,11 @@ class SPECOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
4,
{
{"20m ", 20000000},
@@ -172,20 +172,20 @@ class SPECOptionsView : public View {
}};
Text text_speed{
{9 * 8, 0 * 16, 8 * 8, 1 * 16},
{UI_POS_X(9), UI_POS_Y(0), UI_POS_WIDTH(8), UI_POS_HEIGHT(1)},
"SP /63"};
NumberField field_speed{
{12 * 8, 0 * 16},
{UI_POS_X(12), UI_POS_Y(0)},
2,
{0, 63},
1,
' ',
};
Text text_rx_cal{
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
{UI_POS_X(19), UI_POS_Y(0), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
"Rx_IQ_CAL "};
NumberField field_rx_iq_phase_cal{
{screen_width - 2 * 8, 0 * 16},
{screen_width - 2 * 8, UI_POS_Y(0)},
2,
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
1,
@@ -244,34 +244,34 @@ class AnalogAudioView : public View {
{"previous_zoom"sv, &previous_zoom}, // we are saving and restoring AMFM ZOOM factor from Settings.
}};
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
const Rect options_view_rect{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)};
const Rect nbfm_view_rect{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(18), UI_POS_HEIGHT(1)};
size_t spec_bw_index = 0;
uint32_t spec_bw = 20000000;
uint16_t spec_trigger = 63;
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Audio audio{
{21 * 8, 10, 6 * 8, 4}};
{UI_POS_X(21), 10, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
RxFrequencyField field_frequency{
{5 * 8, 0 * 16},
{UI_POS_X(5), UI_POS_Y(0)},
nav_};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
OptionsField options_modulation{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
4,
{
{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
@@ -283,16 +283,16 @@ class AnalogAudioView : public View {
}};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{screen_width - 2 * 8, UI_POS_Y(0)}};
Text text_ctcss{
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
{UI_POS_X(16), UI_POS_Y(1), UI_POS_WIDTH(14), UI_POS_HEIGHT(1)},
""};
std::unique_ptr<Widget> options_widget{};
RecordView record_view{
{0 * 8, 2 * 16, screen_width, 1 * 16},
{UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
u"AUD",
u"AUDIO",
RecordView::FileType::WAV,
+63 -105
View File
@@ -69,7 +69,7 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
}
void BLECommView::focus() {
options_channel.focus();
field_frequency.focus();
}
BLECommView::BLECommView(NavigationView& nav)
@@ -79,10 +79,7 @@ BLECommView::BLECommView(NavigationView& nav)
&field_rf_amp,
&field_lna,
&field_vga,
&options_channel,
&field_frequency,
&label_send_adv,
&button_send_adv,
&check_log,
&label_packets_sent,
&text_packets_sent,
@@ -90,10 +87,6 @@ BLECommView::BLECommView(NavigationView& nav)
field_frequency.set_step(0);
button_send_adv.on_select = [this](ImageButton&) {
this->toggle();
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
@@ -104,27 +97,8 @@ BLECommView::BLECommView(NavigationView& nav)
logger->append(logs_dir.string() + "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
options_channel.on_change = [this](size_t, int32_t i) {
// If we selected Auto don't do anything and Auto will handle changing.
if (i == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number_rx = i;
channel_number_tx = i;
};
options_channel.set_selected_index(3, true);
logger = std::make_unique<BLECommLogger>();
// Generate new random Mac Address upon each new startup.
generateRandomMacAddress(randomMac);
// Setup Initial Advertise Packet.
advertisePacket = build_adv_packet();
}
@@ -145,86 +119,66 @@ bool BLECommView::in_tx_mode() const {
return (bool)is_running_tx;
}
void BLECommView::toggle() {
if (in_tx_mode()) {
sendAdvertisement(false);
} else {
sendAdvertisement(true);
}
}
BLETxPacket BLECommView::build_adv_packet() {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
std::string dataString = "11094861636b524620506f7274617061636b";
strncpy(bleTxPacket.macAddress, randomMac, 12);
std::string dataString = "02010603030F1807084861636B5246";
strncpy(bleTxPacket.advertisementData, dataString.c_str(), dataString.length());
// Duty cycle of 40% per 100ms advertisment periods.
strncpy(bleTxPacket.packetCount, "80", 3);
bleTxPacket.packet_count = 80;
strncpy(bleTxPacket.packetCount, "1", 2);
bleTxPacket.packet_count = 1;
bleTxPacket.packetType = PKT_TYPE_DISCOVERY;
bleTxPacket.pduType = PKT_TYPE_ADV_IND;
return bleTxPacket;
}
void BLECommView::sendAdvertisement(bool enable) {
if (enable) {
startTx(advertisePacket);
is_adv = true;
} else {
is_adv = false;
stopTx();
}
void BLECommView::sendAdvertisement(void) {
startTx(advertisePacket);
ble_state = Ble_State_Advertising;
}
void BLECommView::startTx(BLETxPacket packetToSend) {
int randomChannel = channel_number_tx;
switch_rx_tx(false);
if (auto_channel) {
int min = 37;
int max = 39;
currentPacket = packetToSend;
packet_counter = currentPacket.packet_count;
randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
switch (advCount) {
case 0:
channel_number_tx = 37;
break;
case 1:
channel_number_tx = 38;
break;
case 2:
channel_number_tx = 39;
break;
}
if (!in_tx_mode()) {
switch_rx_tx(false);
field_frequency.set_value(get_freq_by_channel_number(37));
currentPacket = packetToSend;
packet_counter = currentPacket.packet_count;
advCount++;
button_send_adv.set_bitmap(&bitmap_stop);
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
transmitter_model.set_tx_gain(47);
transmitter_model.enable();
is_running_tx = true;
} else {
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
if (advCount == 3) {
advCount = 0;
}
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
baseband::set_btletx(37, deviceMAC, currentPacket.advertisementData, currentPacket.pduType);
transmitter_model.set_tx_gain(47);
transmitter_model.enable();
is_sending = true;
packet_counter--;
is_running_tx = true;
}
void BLECommView::stopTx() {
button_send_adv.set_bitmap(&bitmap_play);
text_packets_sent.set(to_string_dec_uint(packet_counter));
switch_rx_tx(true);
baseband::set_btlerx(channel_number_rx);
field_frequency.set_value(get_freq_by_channel_number(channel_number_rx));
receiver_model.enable();
is_running_tx = false;
@@ -248,14 +202,12 @@ void BLECommView::on_data(BlePacketData* packet) {
parse_received_packet(packet, (ADV_PDU_TYPE)packet->type);
}
// called each 1/60th of second, so 6 = 100ms
void BLECommView::on_timer() {
// Send advertise burst only once every 100ms
if (++timer_counter == timer_period) {
timer_counter = 0;
if (!is_adv) {
sendAdvertisement(true);
if (ble_state == Ble_State_Receiving || ble_state == Ble_State_Idle) {
sendAdvertisement();
}
}
}
@@ -263,17 +215,18 @@ void BLECommView::on_timer() {
void BLECommView::on_tx_progress(const bool done) {
if (done) {
if (in_tx_mode()) {
is_sending = false;
ble_state = Ble_State_Receiving;
if (packet_counter == 0) {
if (is_adv) {
sendAdvertisement(false);
} else {
stopTx();
}
} else {
startTx(currentPacket);
}
timer_counter = 0;
timer_period = 12;
stopTx();
// else
// {
// startTx(advertisePacket);
// advCount++;
// }
}
}
}
@@ -309,23 +262,28 @@ void BLECommView::parse_received_packet(const BlePacketData* packet, ADV_PDU_TYP
}
if (pdu_type == SCAN_REQ || pdu_type == CONNECT_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
console.clear(true);
std::string str_console = "";
std::string pduTypeStr = "";
if (pdu_type == SCAN_REQ) {
pduTypeStr += "SCAN_REQ";
} else if (pdu_type == CONNECT_REQ) {
pduTypeStr += "CONNECT_REQ";
}
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
str_console += "PACKET TYPE:" + pduTypeStr + "\n";
str_console += "MY MAC:" + to_string_formatted_mac_address(randomMac) + "\n";
str_console += "SCAN MAC:" + to_string_mac_address(directed_mac_data->A1, 6, false) + "\n";
console.write(str_console);
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
std::string directedMAC = to_string_mac_address(directed_mac_data->A1, 6, false);
// Compare directed MAC Hex with the device MAC.
if (1) {
// std::string str_console = "Received SCAN_REQ from directed MAC: " + directedMAC + "\n";
// console.clear(true);
// console.writeln(str_console);
}
} else if (pdu_type == CONNECT_REQ) {
ADV_PDU_PAYLOAD_TYPE_5* connectReq = (ADV_PDU_PAYLOAD_TYPE_5*)packet->data;
std::reverse(connectReq->AdvA, connectReq->AdvA + 6);
std::string directedMAC = to_string_mac_address(connectReq->AdvA, 6, false);
std::string str_console = "Received CONNECT_REQ from directed MAC: " + directedMAC + "\n";
console.clear(true);
console.writeln(str_console);
}
}
}
+20 -19
View File
@@ -59,7 +59,7 @@ class BLECommView : public View {
~BLECommView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
@@ -76,7 +76,16 @@ class BLECommView : public View {
void on_data(BlePacketData* packetData);
void on_tx_progress(const bool done);
void parse_received_packet(const BlePacketData* packet, ADV_PDU_TYPE pdu_type);
void sendAdvertisement(bool enable);
void sendAdvertisement(void);
typedef enum {
Ble_State_Idle = 0,
Ble_State_Advertising = 1,
Ble_State_Receiving = 2,
Ble_State_Sending = 3
} BleState;
BleState ble_state{Ble_State_Idle};
NavigationView& nav_;
@@ -101,8 +110,9 @@ class BLECommView : public View {
uint8_t channel_number_tx = 37;
uint8_t channel_number_rx = 37;
bool auto_channel = false;
uint8_t advCount = 0;
char randomMac[13] = "010203040506";
char deviceMAC[13] = "C23456789ABC";
bool is_running_tx = false;
bool is_sending = false;
@@ -111,7 +121,7 @@ class BLECommView : public View {
int16_t timer_period{6}; // Delay each packet by 16ms.
int16_t timer_counter = 0;
int16_t timer_rx_counter = 0;
int16_t timer_rx_period{12}; // Poll Rx for at least 200ms. (TBD)
int16_t timer_rx_period{12}; // Poll Rx for at least 150ms. (TBD)
uint32_t packet_counter{0};
BLETxPacket advertisePacket{};
@@ -121,7 +131,7 @@ class BLECommView : public View {
static constexpr auto header_height = 5 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
{UI_POS_X(0), 0 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
@@ -129,17 +139,17 @@ class BLECommView : public View {
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
{6 * 8, UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
{16 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
{21 * 8, UI_POS_Y(0)}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
@@ -147,15 +157,6 @@ class BLECommView : public View {
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
Labels label_send_adv{
{{0 * 8, 2 * 8}, "Send Advertisement:", Theme::getInstance()->fg_light->foreground}};
ImageButton button_send_adv{
{21 * 8, 1 * 16, 10 * 8, 2 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Checkbox check_log{
{24 * 8, 2 * 8},
3,
@@ -163,7 +164,7 @@ class BLECommView : public View {
true};
Labels label_packets_sent{
{{0 * 8, 4 * 8}, "Packets Left:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 4 * 8}, "Packets Left:", Theme::getInstance()->fg_light->foreground}};
Text text_packets_sent{
{13 * 8, 2 * 16, 12 * 8, 16},
+286 -146
View File
@@ -36,6 +36,8 @@
#include "ui_textentry.hpp"
#include "usb_serial_asyncmsg.hpp"
#include <string_view>
using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
@@ -179,21 +181,16 @@ void RecentEntriesTable<BleRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string line{};
line.reserve(30);
line.reserve(40);
if (!entry.nameString.empty() && entry.include_name) {
line = entry.nameString;
if (line.length() < 17) {
line += pad_string_with_spaces(17 - line.length());
} else {
line = truncate(line, 17);
}
} else {
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
}
line.resize(columns.at(0).second, ' ');
std::string hitsStr;
@@ -239,15 +236,14 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
text_vendor.set(vendor_name);
text_vendor.set(entry.vendor_name.empty() ? lookup_mac_vendor(entry.packetData.macAddress) : entry.vendor_name);
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
button_send.on_select = [this, &nav](const ui::Button&) {
auto packetToSend = build_packet();
auto packetToSend = build_packet(entry_);
nav.set_on_pop([packetToSend, &nav]() {
nav.replace<BLETxView>(packetToSend);
});
@@ -255,7 +251,7 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
};
button_save.on_select = [this, &nav](const ui::Button&) {
auto packetToSave = build_packet();
auto packetToSave = build_packet(entry_);
packetFileBuffer = "";
text_prompt(
@@ -431,12 +427,11 @@ void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
entry_ = entry;
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
text_vendor.set(vendor_name);
text_vendor.set(entry.vendor_name.empty() ? lookup_mac_vendor(entry.packetData.macAddress) : entry.vendor_name);
set_dirty();
}
BLETxPacket BleRecentEntryDetailView::build_packet() {
BLETxPacket BleRecentEntryDetailView::build_packet(BleRecentEntry entry_) {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
@@ -444,8 +439,8 @@ BLETxPacket BleRecentEntryDetailView::build_packet() {
strncpy(bleTxPacket.macAddress, macAddressStr.c_str(), 12);
strncpy(bleTxPacket.advertisementData, entry_.dataString.c_str(), entry_.packetData.dataLen * 2);
strncpy(bleTxPacket.packetCount, "50", 3);
bleTxPacket.packet_count = 50;
strncpy(bleTxPacket.packetCount, "10", 3);
bleTxPacket.packet_count = 10;
return bleTxPacket;
}
@@ -495,16 +490,18 @@ BLERxView::BLERxView(NavigationView& nav)
&field_vga,
&options_channel,
&field_frequency,
&check_log,
&button_find,
&check_name,
&label_sort,
&options_sort,
&label_found,
&text_found_count,
&check_serial_log,
&button_filter,
&options_filter,
&check_name,
&check_log,
&check_serial_log,
&check_unique,
&check_duplicate_packets,
&button_find,
&label_found,
&text_found_count,
&button_save_list,
&button_clear_list,
&button_switch,
@@ -516,6 +513,29 @@ BLERxView::BLERxView(NavigationView& nav)
nav_.push<BleRecentEntryDetailView>(entry);
};
ensure_directory(find_packet_path);
ensure_directory(log_packets_path);
ensure_directory(packet_save_path);
// ------------------------------------------------------------------------------
// Handle Check Boxes
// ------------------------------------------------------------------------------
logger = std::make_unique<BLELogger>();
check_name.on_select = [this](Checkbox&, bool v) {
name_enable = v;
// update the include_name instance variable value of each entry in recent entries
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
recent_entries_view.set_dirty();
};
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
if (logger && logging)
logger->append(bletx_dir.string() + "/Packets/BLETx_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
@@ -524,12 +544,28 @@ BLERxView::BLERxView(NavigationView& nav)
portapack::async_tx_enabled = false;
}
};
check_unique.on_select = [this](Checkbox&, bool v) {
uniqueParsing = v;
recent.clear();
recent_entries_view.set_dirty();
};
check_duplicate_packets.on_select = [this](Checkbox&, bool v) {
duplicatePackets = v;
recent.clear();
recent_entries_view.set_dirty();
};
check_name.set_value(name_enable);
check_log.set_value(logging);
check_serial_log.set_value(serial_logging);
check_unique.set_value(uniqueParsing);
check_duplicate_packets.set_value(duplicatePackets);
ensure_directory(find_packet_path);
ensure_directory(log_packets_path);
ensure_directory(packet_save_path);
// ------------------------------------------------------------------------------
// Handle Buttons
// ------------------------------------------------------------------------------
filterBuffer = filter;
button_filter.on_select = [this](Button&) {
@@ -543,16 +579,6 @@ BLERxView::BLERxView(NavigationView& nav)
});
};
logger = std::make_unique<BLELogger>();
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
if (logger && logging)
logger->append(blerx_dir.string() + "/Logs/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
check_log.set_value(logging);
button_save_list.on_select = [this, &nav](const ui::Button&) {
listFileBuffer = "";
text_prompt(
@@ -574,17 +600,18 @@ BLERxView::BLERxView(NavigationView& nav)
nav.replace<BLETxView>();
};
field_frequency.set_step(0);
check_name.set_value(name_enable);
check_name.on_select = [this](Checkbox&, bool v) {
name_enable = v;
// update the include_name instance variable value of each entry in recent entries
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
recent_entries_view.set_dirty();
button_find.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
};
};
// ------------------------------------------------------------------------------
// Handle Options
// ------------------------------------------------------------------------------
field_frequency.set_step(0);
options_channel.on_change = [this](size_t index, int32_t v) {
channel_index = (uint8_t)index;
@@ -608,10 +635,9 @@ BLERxView::BLERxView(NavigationView& nav)
};
options_filter.on_change = [this](size_t index, int32_t v) {
(void)v;
filter_index = (uint8_t)index;
recent.clear();
handle_filter_options(v);
uniqueParsing = filter_index == 2 ? true : false;
recent_entries_view.set_dirty();
};
@@ -619,15 +645,6 @@ BLERxView::BLERxView(NavigationView& nav)
options_sort.set_selected_index(sort_index, true);
options_filter.set_selected_index(filter_index, true);
button_find.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
// nav_.set_on_pop([this]() { button_play.focus(); });
};
};
// Auto-configure modem for LCR RX (will be removed later)
baseband::set_btlerx(channel_number);
@@ -785,37 +802,28 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
}
void BLERxView::on_data(BlePacketData* packet) {
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
uint64_t uniqueKeyEncoded = copy_mac_address_to_uint64(packet->macAddress);
// Start of Packet stuffing.
// Masking off the top 2 bytes to avoid invalid keys.
uint64_t key = macAddressEncoded & 0xFFFFFFFFFFFF;
bool packetExists = false;
uint64_t key = (uniqueKeyEncoded & 0xFFFFFFFFFFFF);
if (duplicatePackets) {
key |= ((uint64_t)packet->type) << 48;
}
// If found store into tempEntry to modify.
auto it = find(recent, key);
if (it != recent.end()) {
recent.push_front(*it);
recent.erase(it);
updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
packetExists = true;
} else {
recent.emplace_front(key);
truncate_entries(recent);
packetExists = updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
// If parsing failed, remove entry.
if (!packetExists) {
recent.erase(recent.begin());
}
}
if (packetExists) {
handle_filter_options(options_filter.selected_index());
handle_entries_sort(options_sort.selected_index());
if (updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type)) {
if (!searchList.empty()) {
auto it = searchList.begin();
@@ -833,24 +841,31 @@ void BLERxView::on_data(BlePacketData* packet) {
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
}
// Packets were updated, resort the list.
handle_entries_sort(options_sort.selected_index());
} else {
recent.pop_front();
}
log_ble_packet(packet);
}
void BLERxView::log_ble_packet(BlePacketData* packet) {
str_console = "";
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
str_console += " Mac:";
str_console += to_string_mac_address(packet->macAddress, 6, false);
str_console += " Data:";
if ((logger && logging) || serial_logging) {
str_console = "";
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
str_console += " Mac:";
str_console += to_string_mac_address(packet->macAddress, 6, false);
str_console += " Data:";
int i;
int i;
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
}
}
// Log at End of Packet.
@@ -866,12 +881,8 @@ void BLERxView::log_ble_packet(BlePacketData* packet) {
void BLERxView::on_filter_change(std::string value) {
// New filter? Reset list from recent entries.
if (filter != value) {
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
// // return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos) && (to_string_mac_address(entry.packetData.macAddress, 6, false).find(value) == std::string::npos);
// });
filter = value;
handle_filter_options(options_filter.selected_index());
recent.clear();
}
}
@@ -947,7 +958,7 @@ void BLERxView::handle_entries_sort(uint8_t index) {
switch (index) {
case 0:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
recent, [](const BleRecentEntry& entry) { return entry.uniqueKey & 0xFFFFFFFFFFFF; }, true);
break;
case 1:
sortEntriesBy(
@@ -965,6 +976,10 @@ void BLERxView::handle_entries_sort(uint8_t index) {
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.nameString; }, true);
break;
case 5:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.informationString; }, true);
break;
default:
break;
}
@@ -972,21 +987,31 @@ void BLERxView::handle_entries_sort(uint8_t index) {
recent_entries_view.set_dirty();
}
void BLERxView::handle_filter_options(uint8_t index) {
auto value = filter;
bool BLERxView::handle_filter_options(uint8_t index, const BleRecentEntry& entry) {
const std::string& value = filter; // no copy
if (value.empty()) return true;
switch (index) {
case 0: // filter by Data
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
});
break;
case 1: // filter by MAC address (All caps: e.g. AA:BB:CC:DD:EE:FF)
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (to_string_mac_address(entry.packetData.macAddress, 6, false).find(value) == std::string::npos);
});
break;
case 0: // Data OR Name
return std::string_view(entry.dataString).find(value) != std::string_view::npos || std::string_view(entry.nameString).find(value) != std::string_view::npos;
case 1: // MAC
return std::string_view(to_string_mac_address(entry.packetData.macAddress, 6, false)).find(value) != std::string_view::npos;
case 2: // Name
return std::string_view(entry.nameString).find(value) != std::string_view::npos;
case 3: // Info
return std::string_view(entry.informationString).find(value) != std::string_view::npos;
case 4: // Vendor
return std::string_view(entry.vendor_name).find(value) != std::string_view::npos;
case 5: // Channel (string once, not per call)
return std::string_view(to_string_dec_int(entry.channelNumber)).find(value) != std::string_view::npos;
default:
break;
return true;
}
}
@@ -1007,60 +1032,175 @@ bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
bool success = false;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
entry.packetData.type = packet->type;
entry.packetData.size = packet->size;
entry.packetData.dataLen = packet->dataLen;
// Mac Address of sender.
entry.packetData.macAddress[0] = packet->macAddress[0];
entry.packetData.macAddress[1] = packet->macAddress[1];
entry.packetData.macAddress[2] = packet->macAddress[2];
entry.packetData.macAddress[3] = packet->macAddress[3];
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
entry.numHits++;
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
std::string vendor_name;
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, vendor_name);
}
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
entry.packetData.data[i] = packet->data[i];
}
entry.include_name = check_name.value();
// Only parse name for advertisment packets and empty name entries
if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
if (uniqueParsing) {
// Add your unique beacon parsing function here.
success = parse_tracking_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
}
if (!success && !uniqueParsing) {
success = parse_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
}
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
reverse_byte_array(directed_mac_data->A1, 6);
if (!uniqueParsing) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
reverse_byte_array(directed_mac_data->A1, 6);
success = true;
}
}
return success;
if (success) {
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
entry.packetData.type = packet->type;
entry.packetData.size = packet->size;
entry.packetData.dataLen = packet->dataLen;
// Mac Address of sender.
entry.packetData.macAddress[0] = packet->macAddress[0];
entry.packetData.macAddress[1] = packet->macAddress[1];
entry.packetData.macAddress[2] = packet->macAddress[2];
entry.packetData.macAddress[3] = packet->macAddress[3];
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
entry.numHits++;
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, entry.vendor_name);
}
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
entry.packetData.data[i] = packet->data[i];
}
entry.include_name = check_name.value();
}
return handle_filter_options(options_filter.selected_index(), entry) && success;
}
bool BLERxView::parse_tracking_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
uint8_t currentByte, currentLength, currentType = 0;
for (currentByte = 0; currentByte < length;) {
currentLength = data[currentByte++];
currentType = data[currentByte++];
// Manufacturer Specific Data (0xFF)
if (currentType == 0xFF && currentLength >= 4) {
uint16_t companyID = data[currentByte] | (data[currentByte + 1] << 8);
// Apple AirTag / Find My
if (companyID == 0x004C && currentLength >= 6) {
uint8_t appleType = data[currentByte + 2];
uint8_t appleLen = data[currentByte + 3];
if (appleType == 0x12 && appleLen == 0x19 && currentLength >= 4 + appleLen) {
nameString.assign("Apple AirTag");
informationString.assign("Find My");
return true;
} else if (appleType == 0x02 && appleLen == 0x15) {
uint16_t major = (data[currentByte + 20] << 8) | data[currentByte + 21];
uint16_t minor = (data[currentByte + 22] << 8) | data[currentByte + 23];
nameString.assign("iBeacon");
informationString.assign(to_string_hex(major) + to_string_hex(minor));
return true;
}
}
}
// Services
else if ((currentType == 0x02 || currentType == 0x03) && currentLength >= 3) {
for (int u = 0; u < currentLength - 1; u += 2) {
uint16_t uuid16 = data[currentByte + u] | (data[currentByte + u + 1] << 8);
if (uuid16 == 0x1802) { // Immediate Alert Service = Find Me Profile
nameString.assign("FindMe");
informationString.assign("IAS");
return true;
}
}
}
// Service Data
else if (currentType == 0x16 && currentLength >= 3) {
uint16_t uuid16 = data[currentByte] | (data[currentByte + 1] << 8);
switch (uuid16) {
case 0xFD59: { // Samsung SmartTag - Unregistered
nameString.assign("Samsung SmartTag");
informationString.assign("Unreg");
return true;
}
case 0xFD5A: { // Samsung SmartTag - Registered
nameString.assign("Samsung SmartTag");
informationString.assign("Reg");
return true;
}
case 0xFD84: {
if (currentByte + 2 < length) {
uint8_t model = data[currentByte + 2];
switch (model) {
case 0x01:
informationString.assign("Mate");
break;
case 0x02:
informationString.assign("Pro");
break;
case 0x03:
informationString.assign("Slim");
break;
case 0x04:
informationString.assign("Sticker");
break;
default:
informationString.assign("Unknown");
break;
}
}
nameString.assign("Tile");
return true;
}
case 0xFEAA: {
if (currentByte + 2 < length) {
uint8_t frameType = data[currentByte + 2];
switch (frameType) {
case 0x00:
informationString.assign("UID");
break;
case 0x10:
informationString.assign("URL");
break;
case 0x20:
informationString.assign("TLM");
break;
case 0x30:
informationString.assign("EID");
break;
default:
informationString.assign("Unknown");
break;
}
}
nameString.assign("Eddystone");
return true;
}
}
}
currentByte += (currentLength - 1);
}
return false;
}
bool BLERxView::parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
+73 -53
View File
@@ -71,7 +71,8 @@ typedef enum {
RESERVED5 = 12,
RESERVED6 = 13,
RESERVED7 = 14,
RESERVED8 = 15
RESERVED8 = 15,
UNKNOWN = 16
} ADV_PDU_TYPE;
typedef enum {
@@ -84,9 +85,9 @@ typedef enum {
struct BleRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0xFFFFFFFFFFFF;
static constexpr Key invalid_key = 0xFFFFFFFFFFFFF;
uint64_t macAddress;
uint64_t uniqueKey;
int dbValue;
BlePacketData packetData;
std::string timestamp;
@@ -98,6 +99,7 @@ struct BleRecentEntry {
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
MAC_VENDOR_STATUS vendor_status;
std::string vendor_name;
bool entryFound;
BleRecentEntry()
@@ -105,8 +107,8 @@ struct BleRecentEntry {
}
BleRecentEntry(
const uint64_t macAddress)
: macAddress{macAddress},
const uint64_t uniqueKey)
: uniqueKey{uniqueKey},
dbValue{},
packetData{},
timestamp{},
@@ -118,11 +120,12 @@ struct BleRecentEntry {
pduType{},
channelNumber{},
vendor_status{MAC_VENDOR_UNKNOWN},
vendor_name{},
entryFound{} {
}
Key key() const {
return macAddress;
return uniqueKey;
}
};
@@ -139,11 +142,11 @@ class BleRecentEntryDetailView : public View {
void update_data();
void focus() override;
void paint(Painter&) override;
static BLETxPacket build_packet(BleRecentEntry entry_);
private:
NavigationView& nav_;
BleRecentEntry entry_{};
BLETxPacket build_packet();
void on_save_file(const std::string value, BLETxPacket packetToSave);
bool saveFile(const std::filesystem::path& path, BLETxPacket packetToSave);
std::string packetFileBuffer{};
@@ -152,42 +155,42 @@ class BleRecentEntryDetailView : public View {
static constexpr uint8_t total_data_lines{5};
Labels label_mac_address{
{{0 * 8, 0 * 16}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(0)}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
Text text_mac_address{
{12 * 8, 0 * 16, 17 * 8, 16},
{UI_POS_X(12), UI_POS_Y(0), UI_POS_WIDTH(17), UI_POS_HEIGHT(1)},
"-"};
Labels label_pdu_type{
{{0 * 8, 1 * 16}, "PDU Type:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(1)}, "PDU Type:", Theme::getInstance()->fg_light->foreground}};
Text text_pdu_type{
{9 * 8, 1 * 16, 17 * 8, 16},
{9 * 8, UI_POS_Y(1), 17 * 8, UI_POS_HEIGHT(1)},
"-"};
Labels label_vendor{
{{0 * 8, 2 * 16}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(2)}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
Text text_vendor{
{7 * 8, 2 * 16, 23 * 8, 16},
{7 * 8, UI_POS_Y(2), 23 * 8, UI_POS_HEIGHT(1)},
"-"};
Labels labels{
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
{{10 * 8, 3 * 16}, "Value", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(3)}, "Len", Theme::getInstance()->fg_light->foreground},
{{5 * 8, UI_POS_Y(3)}, "Type", Theme::getInstance()->fg_light->foreground},
{{10 * 8, UI_POS_Y(3)}, "Value", Theme::getInstance()->fg_light->foreground},
};
Button button_send{
{19, 224, 96, 24},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 224, 96, 24},
"Send"};
Button button_done{
{125, 224, 96, 24},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 224, 96, 24},
"Done"};
Button button_save{
{72, 264, 96, 24},
{UI_POS_X_CENTER(12), 264, 96, 24},
"Save"};
bool send_updates{false};
@@ -206,7 +209,7 @@ class BLERxView : public View {
~BLERxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
@@ -224,9 +227,10 @@ class BLERxView : public View {
void file_error();
void on_timer();
void handle_entries_sort(uint8_t index);
void handle_filter_options(uint8_t index);
bool handle_filter_options(uint8_t index, const BleRecentEntry& entry);
bool updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
bool parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
bool parse_tracking_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
NavigationView& nav_;
@@ -240,6 +244,7 @@ class BLERxView : public View {
uint8_t sort_index{0};
uint8_t filter_index{0};
bool uniqueParsing = false;
bool duplicatePackets = false;
std::string filter{};
bool logging{false};
bool serial_logging{false};
@@ -258,6 +263,8 @@ class BLERxView : public View {
// disabled to always start without USB serial activated until we can make it non blocking if not connected
// {"serial_log"sv, &serial_logging},
{"name"sv, &name_enable},
{"unique_parsing"sv, &uniqueParsing},
{"duplicate_packets"sv, &duplicatePackets},
}};
std::string str_console = "";
@@ -267,7 +274,7 @@ class BLERxView : public View {
bool auto_channel = false;
int16_t timer_count{0};
int16_t timer_period{2}; // 25ms
int16_t timer_period{1}; // 25ms
std::string filterBuffer{};
std::string listFileBuffer{};
@@ -283,11 +290,11 @@ class BLERxView : public View {
std::filesystem::path log_packets_path{blerx_dir / u"Logs/????.TXT"};
std::filesystem::path packet_save_path{blerx_dir / u"Lists/????.csv"};
static constexpr auto header_height = 9 * 8;
static constexpr auto header_height = 12 * 8;
static constexpr auto switch_button_height = 3 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
{UI_POS_X(0), UI_POS_Y(0)},
5,
{{"Ch.37", 37},
{"Ch.38", 38},
@@ -295,46 +302,50 @@ class BLERxView : public View {
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
{UI_POS_X(6), UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
{UI_POS_X(16), UI_POS_Y(0)}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
{UI_POS_X(21), UI_POS_Y(0)}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(6), 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(6), 4}};
Labels label_sort{
{{0 * 8, 2 * 8}, "Sort:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(1)}, "Sort:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_sort{
{5 * 8, 2 * 8},
{5 * 8, UI_POS_Y(1)},
4,
{{"MAC", 0},
{"Hits", 1},
{"dB", 2},
{"Time", 3},
{"Name", 4}}};
{"Name", 4},
{"Info", 5}}};
Button button_filter{
{11 * 8, 2 * 8, 7 * 8, 16},
{11 * 8, UI_POS_Y(1), 7 * 8, 16},
"Filter:"};
OptionsField options_filter{
{18 * 8 + 2, 2 * 8},
{18 * 8 + 2, UI_POS_Y(1)},
7,
{{"Data", 0},
{"MAC", 1},
{"Unique", 2}}};
{"Name", 2},
{"Info", 3},
{"Vendor", 4},
{"Channel", 5}}};
Checkbox check_log{
{10 * 8, 4 * 8 + 2},
@@ -343,41 +354,50 @@ class BLERxView : public View {
true};
Checkbox check_name{
{0 * 8, 4 * 8 + 2},
{UI_POS_X(0), 4 * 8 + 2},
3,
"Name",
true};
Button button_find{
{0 * 8, 7 * 8 - 2, 4 * 8, 16},
"Find"};
Labels label_found{
{{5 * 8, 7 * 8 - 2}, "Found:", Theme::getInstance()->fg_light->foreground}};
Text text_found_count{
{11 * 8, 7 * 8 - 2, 20 * 8, 16},
"0/0"};
Checkbox check_serial_log{
{18 * 8 + 2, 4 * 8 + 2},
7,
"USB Log",
true};
// Console console{
// {0, 10 * 8, screen_height, screen_height-80}};
Checkbox check_unique{
{0 * 8 + 2, 7 * 8 + 2},
7,
"Unique",
true};
Checkbox check_duplicate_packets{
{10 * 8 + 2, 7 * 8 + 2},
7,
"Duplicate",
true};
Button button_find{
{UI_POS_X(0), 10 * 8 - 2, 4 * 8, 16},
"Find"};
Labels label_found{
{{5 * 8, 10 * 8 - 2}, "Found:", Theme::getInstance()->fg_light->foreground}};
Text text_found_count{
{11 * 8, 10 * 8 - 2, 20 * 8, 16},
"0/0"};
Button button_clear_list{
{2 * 8, screen_height - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
{11 * 8, screen_height - (16 + 32), 11 * 8, 32},
{UI_POS_X_CENTER(11), screen_height - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
{screen_width - 6 * 8, screen_height - (16 + 32), 4 * 8, 32},
{UI_POS_X_RIGHT(6), screen_height - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
@@ -387,7 +407,7 @@ class BLERxView : public View {
BleRecentEntries tempList{};
RecentEntriesColumns columns{{
{"Name", 17},
{"Name", 0},
{"Hits", 7},
{"dBm", 4},
}};
+117 -80
View File
@@ -35,6 +35,7 @@
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "usb_serial_asyncmsg.hpp"
using namespace portapack;
using namespace modems;
@@ -123,6 +124,31 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
namespace ui {
PKT_TYPE get_pkt_type_from_string(const std::string& type_str) {
if (type_str == "RAW") {
return PKT_TYPE_RAW;
} else if (type_str == "DISCOVERY") {
return PKT_TYPE_DISCOVERY;
} else if (type_str == "IBEACON") {
return PKT_TYPE_IBEACON;
} else if (type_str == "ADV_IND") {
return PKT_TYPE_ADV_IND;
} else if (type_str == "ADV_DIRECT_IND") {
return PKT_TYPE_ADV_DIRECT_IND;
} else if (type_str == "ADV_NONCONN_IND") {
return PKT_TYPE_ADV_NONCONN_IND;
} else if (type_str == "ADV_SCAN_IND") {
return PKT_TYPE_ADV_SCAN_IND;
} else if (type_str == "SCAN_REQ") {
return PKT_TYPE_SCAN_REQ;
} else if (type_str == "SCAN_RSP") {
return PKT_TYPE_SCAN_RSP;
} else if (type_str == "CONNECT_REQ") {
return PKT_TYPE_CONNECT_REQ;
}
return PKT_TYPE_INVALID_TYPE;
}
void BLETxView::focus() {
button_open.focus();
}
@@ -167,28 +193,11 @@ void BLETxView::toggle() {
}
}
void BLETxView::start() {
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
void BLETxView::send_packet() {
// Generate new random Mac Address.
transmitter_model.enable();
generateRandomMacAddress(randomMac);
// If this is our first run, check file.
if (!is_active()) {
File data_file;
auto error = data_file.open(file_path);
if (error && !file_override) {
file_error();
check_loop.set_value(false);
return;
}
button_play.set_bitmap(&bitmap_stop);
is_running = true;
}
char advertisementData[63] = {0};
strcpy(advertisementData, packets[current_packet].advertisementData);
@@ -235,9 +244,28 @@ void BLETxView::start() {
}
}
// Setup next packet configuration.
progressbar.set_max(packets[current_packet].packet_count);
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, pduType);
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
text_packets_sent.set(to_string_dec_uint(packet_counter));
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, packets[current_packet].pduType);
packetDone = false;
}
void BLETxView::start() {
if (file_path.empty()) {
file_error();
check_loop.set_value(false);
return;
}
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
button_play.set_bitmap(&bitmap_stop);
is_running = true;
send_packet();
}
void BLETxView::stop() {
@@ -253,66 +281,69 @@ void BLETxView::stop() {
is_running = false;
}
void BLETxView::reset() {
transmitter_model.disable();
baseband::shutdown();
start();
}
// called each 1/60th of second, so 6 = 100ms
void BLETxView::on_timer() {
if (++timer_count == timer_period) {
timer_count = 0;
if (is_active()) {
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
if (current_packet == (num_packets - 1)) {
current_packet = 0;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
reset();
} else {
stop();
}
} else {
current_packet++;
update_current_packet(packets[current_packet], current_packet);
reset();
}
} else {
reset();
}
}
}
if (++auto_channel_counter == auto_channel_period) {
auto_channel_counter = 0;
if (auto_channel) {
int min = 37;
int max = 39;
channel_number = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(channel_number));
if (is_active() && packetDone) {
send_packet();
}
}
}
void BLETxView::on_tx_progress(const bool done) {
void BLETxView::on_tx_progress(const bool done, uint32_t progress) {
// TODO: make use of progress variable
(void)progress;
if (done) {
if (is_active()) {
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
transmitter_model.disable();
if (auto_channel) {
switch (advCount) {
case 0:
channel_number = 37;
break;
case 1:
channel_number = 38;
break;
case 2:
channel_number = 39;
break;
}
packet_counter--;
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
field_frequency.set_value(get_freq_by_channel_number(channel_number));
if (advCount == 3) {
channel_number = 37;
packet_counter--;
packetDone = true;
advCount = 0;
} else {
send_packet();
advCount++;
}
} else {
packet_counter--;
packetDone = true;
}
}
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
if (current_packet == (num_packets - 1)) {
current_packet = 0;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
} else {
stop();
}
} else {
current_packet++;
update_current_packet(packets[current_packet], current_packet);
}
}
}
}
@@ -331,7 +362,6 @@ BLETxView::BLETxView(NavigationView& nav)
&label_speed,
&options_speed,
&options_channel,
&options_adv_type,
&label_marked_data,
&marked_data_sequence,
&label_packet_index,
@@ -371,13 +401,8 @@ BLETxView::BLETxView(NavigationView& nav)
timer_count = 0;
};
options_adv_type.on_change = [this](size_t, int32_t i) {
pduType = (PKT_TYPE)i;
};
options_speed.set_selected_index(0);
options_channel.set_selected_index(0);
options_adv_type.set_selected_index(0);
options_channel.set_selected_index(3);
check_rand_mac.set_value(false);
check_rand_mac.on_select = [this](Checkbox&, bool v) {
@@ -482,17 +507,25 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
do {
readUntil(data_file, packets[num_packets].macAddress, mac_address_size_str, ' ');
readUntil(data_file, packets[num_packets].advertisementData, max_packet_size_str, ' ');
readUntil(data_file, packets[num_packets].packetType, max_packet_type_str, ' ');
readUntil(data_file, packets[num_packets].packetCount, max_packet_repeat_str, '\n');
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
uint64_t packetCountSize = strlen(packets[num_packets].packetCount);
uint64_t packetTypeSize = strlen(packets[num_packets].packetType);
packets[num_packets].packet_count = stringToUint32(packets[num_packets].packetCount);
packets[num_packets].pduType = (PKT_TYPE)get_pkt_type_from_string(packets[num_packets].packetType);
// Verify Data.
if ((macAddressSize == mac_address_size_str) && (advertisementDataSize < max_packet_size_str) && (packetCountSize < max_packet_repeat_str) &&
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) && hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) && (packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count)) {
if ((macAddressSize == mac_address_size_str) &&
(advertisementDataSize < max_packet_size_str) &&
(packetCountSize < max_packet_repeat_str) &&
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) &&
hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) &&
(packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count) &&
(packetTypeSize <= max_packet_type_str)) {
text_filename.set(truncate(file_path.filename().string(), 12));
} else {
@@ -510,6 +543,8 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
} while (num_packets < max_num_packets);
update_current_packet(packets[0], 0);
data_file.close();
}
}
@@ -540,6 +575,8 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
text_mac_address.set(formattedMacAddress);
progressbar.set_max(packet.packet_count);
packet_counter = packet.packet_count;
current_packet = currentIndex;
@@ -550,7 +587,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
dataFile.write("\n", 1);
}
dataFile.~File();
dataFile.close();
auto result = FileWrapper::open(dataTempFilePath);
+34 -29
View File
@@ -38,6 +38,8 @@
#include "utility.hpp"
#include "file_path.hpp"
#include "ble_tx_app.hpp"
#include "recent_entries.hpp"
#include <string>
@@ -91,8 +93,9 @@ struct BLETxPacket {
char macAddress[13];
char advertisementData[63];
char packetCount[11];
char packetType[17];
uint32_t packet_count;
PKT_TYPE packetType;
PKT_TYPE pduType;
};
class BLETxView : public View {
@@ -102,15 +105,15 @@ class BLETxView : public View {
~BLETxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
bool is_active() const;
void toggle();
void start();
void send_packet();
void stop();
void reset();
void handle_replay_thread_done(const uint32_t return_code);
void file_error();
bool saveFile(const std::filesystem::path& path);
@@ -122,7 +125,7 @@ class BLETxView : public View {
void on_data(uint32_t value, bool is_data);
void on_file_changed(const std::filesystem::path& new_file_path);
void on_save_file(const std::string value);
void on_tx_progress(const bool done);
void on_tx_progress(const bool done, uint32_t progress);
void on_random_data_change(std::string value);
void update_current_packet(BLETxPacket packet, uint32_t currentIndex);
@@ -156,6 +159,8 @@ class BLETxView : public View {
uint32_t packet_counter{0};
uint32_t num_packets{0};
uint32_t current_packet{0};
uint8_t packetTxCount{0};
bool packetDone = false;
bool random_mac = false;
bool file_override = false;
@@ -170,39 +175,39 @@ class BLETxView : public View {
std::vector<uint16_t> markedBytes{};
CursorPos cursor_pos{};
uint8_t marked_counter = 0;
uint8_t advCount = 0;
static constexpr uint8_t mac_address_size_str{12};
static constexpr uint8_t max_packet_size_str{62};
static constexpr uint8_t max_packet_repeat_str{10};
static constexpr uint8_t max_packet_type_str{16};
static constexpr uint32_t max_packet_repeat_count{UINT32_MAX};
static constexpr uint32_t max_num_packets{32};
BLETxPacket packets[max_num_packets];
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
static constexpr auto header_height = 10 * 16;
static constexpr auto switch_button_height = 6 * 16;
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y(0), 10 * 8, 2 * 16},
"Open file"};
Text text_filename{
{11 * 8, 0 * 16, 12 * 8, 16},
{11 * 8, UI_POS_Y(0), UI_POS_WIDTH_REMAINING(11), 16},
"-"};
ProgressBar progressbar{
{11 * 8, 1 * 16, 9 * 8, 16}};
{11 * 8, 1 * 16, UI_POS_WIDTH_REMAINING(11 + 10), 16}};
Checkbox check_rand_mac{
{21 * 8, 1 * 16},
{UI_POS_X_RIGHT(9), 1 * 16},
6,
"?? Mac",
true};
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
{UI_POS_X(0), 2 * 16},
nav_};
TransmitterView2 tx_view{
@@ -210,28 +215,28 @@ class BLETxView : public View {
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
{UI_POS_X_RIGHT(9), 2 * 16},
4,
"Loop",
true};
ImageButton button_play{
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
{UI_POS_X_RIGHT(2), 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Labels label_speed{
{{0 * 8, 6 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 6 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_speed{
{7 * 8, 6 * 8},
3,
{{"1 ", 1}, // 16ms
{"2 ", 2}, // 32ms
{"3 ", 3}, // 48ms
{"4 ", 6}, // 100ms
{"5 ", 12}}}; // 200ms
{{"1 ", 2}, // 25ms
{"2 ", 4}, // 50ms
{"3 ", 6}, // 75ms
{"4 ", 8}, // 100ms
{"5 ", 12}}}; // 150ms
OptionsField options_channel{
{11 * 8, 6 * 8},
@@ -254,7 +259,7 @@ class BLETxView : public View {
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
Labels label_marked_data{
{{0 * 8, 4 * 16}, "Marked Data:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 4 * 16}, "Marked Data:", Theme::getInstance()->fg_light->foreground}};
OptionsField marked_data_sequence{
{12 * 8, 8 * 8},
@@ -264,42 +269,42 @@ class BLETxView : public View {
{"Random", 2}}};
Labels label_packet_index{
{{0 * 8, 12 * 8}, "Packet Index:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 12 * 8}, "Packet Index:", Theme::getInstance()->fg_light->foreground}};
Text text_packet_index{
{13 * 8, 6 * 16, 12 * 8, 16},
"-"};
Labels label_packets_sent{
{{0 * 8, 14 * 8}, "Repeat Count:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 14 * 8}, "Repeat Count:", Theme::getInstance()->fg_light->foreground}};
Text text_packets_sent{
{13 * 8, 7 * 16, 12 * 8, 16},
"-"};
Labels label_mac_address{
{{0 * 8, 16 * 8}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 16 * 8}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
Text text_mac_address{
{12 * 8, 8 * 16, 20 * 8, 16},
"-"};
Labels label_data_packet{
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
TextViewer dataEditView{
{0, 9 * 18, screen_width, screen_height - 80}};
{0, 9 * 18, 240, 240}};
Button button_clear_marked{
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
{UI_POS_X(1), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(13), UI_POS_HEIGHT(1.5)},
"Clear Marked"};
Button button_save_packet{
{1 * 8, 16 * 16, 13 * 8, 2 * 16},
{UI_POS_X(1), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
"Save Packet"};
Button button_switch{
{16 * 8, 16 * 16, 13 * 8, 2 * 16},
{UI_POS_X_RIGHT(14), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
"Switch to Rx"};
std::string str_log{""};
@@ -320,7 +325,7 @@ class BLETxView : public View {
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
this->on_tx_progress(message.done, message.progress);
}};
MessageHandlerRegistration message_handler_frame_sync{
+9 -9
View File
@@ -65,31 +65,31 @@ class CaptureAppView : public View {
}};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 1 * 16}, "Format:", Theme::getInstance()->fg_light->foreground},
};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(24), 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
FrequencyStepView field_frequency_step{
{10 * 8, 0 * 16}};
{10 * 8, UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
{16 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
{21 * 8, UI_POS_Y(0)}};
OptionsField option_bandwidth{
{5 * 8, 1 * 16},
@@ -109,7 +109,7 @@ class CaptureAppView : public View {
/*small*/ true};
RecordView record_view{
{0 * 8, 2 * 16, screen_width, 1 * 16},
{UI_POS_X(0), 2 * 16, screen_width, 1 * 16},
u"BBD_????.*",
captures_dir,
RecordView::FileType::RawS16,
+5 -2
View File
@@ -56,6 +56,7 @@ POCSAGSettingsView::POCSAGSettingsView(
: settings_{settings} {
add_children(
{&labels,
&opt_baud_rate,
&check_log,
&check_log_raw,
&check_small_font,
@@ -65,6 +66,7 @@ POCSAGSettingsView::POCSAGSettingsView(
&field_filter_address,
&button_save});
opt_baud_rate.set_by_value(settings_.baud_rate);
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);
@@ -81,7 +83,7 @@ POCSAGSettingsView::POCSAGSettingsView(
settings_.hide_addr_only = check_hide_addr_only.value();
settings_.filter_mode = opt_filter_mode.selected_index_value();
settings_.filter_address = field_filter_address.to_integer();
settings_.baud_rate = opt_baud_rate.selected_index_value();
nav.pop();
};
}
@@ -142,7 +144,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
audio::output::start();
receiver_model.enable();
baseband::set_pocsag();
baseband::set_pocsag((int8_t)settings_.baud_rate);
}
void POCSAGAppView::focus() {
@@ -182,6 +184,7 @@ void POCSAGAppView::refresh_ui() {
btn_text = "Filter Last";
break;
}
baseband::set_pocsag((int8_t)settings_.baud_rate);
button_filter_last.set_text(btn_text);
}
+20 -9
View File
@@ -126,6 +126,7 @@ struct POCSAGSettings {
bool hide_bad_data = false;
bool hide_addr_only = false;
uint8_t filter_mode = false;
int32_t baud_rate = -1;
uint32_t filter_address = 0;
};
@@ -139,7 +140,16 @@ class POCSAGSettingsView : public View {
private:
POCSAGSettings& settings_;
OptionsField opt_baud_rate{
{8 * 8, 0 * 16},
4,
{{"Auto", -1},
{" 512", 0},
{"1200", 1},
{"2400", 2}}};
Labels labels{
{{2 * 8, 0 * 16}, "Baud:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 12 * 16}, "Filter Mode:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 13 * 16}, "Filter Addr:", Theme::getInstance()->fg_light->foreground},
};
@@ -183,7 +193,7 @@ class POCSAGSettingsView : public View {
true /*explicit_edit*/};
Button button_save{
{11 * 8, 16 * 16, 10 * 8, 2 * 16},
{UI_POS_X_CENTER(10), UI_POS_Y(16), 10 * 8, 2 * 16},
"Save"};
};
@@ -221,6 +231,7 @@ class POCSAGAppView : public View {
{"filter_address"sv, &settings_.filter_address},
{"hide_bad_data"sv, &settings_.hide_bad_data},
{"hide_addr_only"sv, &settings_.hide_addr_only},
{"baud_rate"sv, &settings_.baud_rate},
}};
void refresh_ui();
@@ -236,30 +247,30 @@ class POCSAGAppView : public View {
uint16_t packet_count = 0;
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
{UI_POS_X(0), 0 * 8},
nav_};
RFAmpField field_rf_amp{
{11 * 8, 0 * 16}};
{11 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{16 * 8, 0 * 16}};
{16 * 8, UI_POS_Y(0)}};
RSSI rssi{
{19 * 8 - 4, 3, 6 * 8, 4}};
{19 * 8 - 4, 3, UI_POS_WIDTH_REMAINING(26), 4}};
Audio audio{
{19 * 8 - 4, 8, 6 * 8, 4}};
{19 * 8 - 4, 8, UI_POS_WIDTH_REMAINING(26), 4}};
NumberField field_squelch{
{25 * 8, 0 * 16},
{UI_POS_X_RIGHT(6), UI_POS_Y(0)},
2,
{0, 99},
1,
' ',
true /*wrap*/};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Image image_status{
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
@@ -10,7 +10,7 @@ namespace ui {
constexpr std::string_view mayhem_information_list[] = {
"#****** Mayhem Community ******",
" ",
" https://discord.mayhem.app",
" https://discord.hackrf.app",
" ",
"#**** List of contributors ****",
" ",
+11 -8
View File
@@ -51,7 +51,8 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
Color target_color;
std::string entry_string;
@@ -70,9 +71,11 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
target_color = Theme::getInstance()->fg_medium->foreground;
};
entry_string +=
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
std::string ipc = (entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ");
uint8_t firstcolwidth = columns.at(0).second;
ipc.resize(firstcolwidth, ' '); // Make sure this is always match the first column's width that is dynamic.
entry_string += ipc + to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
// It is generally accepted that for every thousand feet of altitude,
@@ -100,7 +103,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
entry_string);
if (entry.pos.pos_valid)
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
painter.draw_bitmap(target_rect.location() + Point(firstcolwidth * 8 - 8, 0),
bitmap_target, target_color, style.background);
}
@@ -409,7 +412,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
&status_good_frame,
&field_volume});
recent_entries_view.set_parent_rect({0, 16, screen_width, 272});
recent_entries_view.set_parent_rect({0, 16, screen_width, UI_POS_HEIGHT_REMAINING(2)});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detail_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(entry);
@@ -635,14 +638,14 @@ void ADSBRxView::refresh_ui() {
if (details_view->map_active()) {
// Is it time to clear and refresh the map's markers?
if (ticks_since_marker_refresh >= MARKER_UPDATE_SECONDS) {
if (ticks_since_marker_refresh >= (details_view->get_map_type() == MAP_TYPE_OSM ? MARKER_UPDATE_SECONDS_OSM : MARKER_UPDATE_SECONDS_BIN)) {
map_needs_update = true;
ticks_since_marker_refresh = 0;
details_view->clear_map_markers();
}
} else {
// Refresh map immediately once active.
ticks_since_marker_refresh = MARKER_UPDATE_SECONDS;
ticks_since_marker_refresh = MARKER_UPDATE_SECONDS_OSM; // this is the bigger, so set it to force
}
// Process the entries list.
+57 -49
View File
@@ -64,8 +64,9 @@ namespace ui {
#define VEL_AIR_SUBSONIC 3
#define VEL_AIR_SUPERSONIC 4
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define MARKER_UPDATE_SECONDS 5 // "other" map marker redraw interval
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define MARKER_UPDATE_SECONDS_OSM 10 // "other" map marker redraw interval for osm
#define MARKER_UPDATE_SECONDS_BIN 5 // "other" map marker redraw interval for bin map
/* Thresholds (in seconds) that define the transition between ages. */
struct ADSBAgeLimit {
@@ -207,54 +208,54 @@ class ADSBRxAircraftDetailsView : public View {
private:
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Registration:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Manufacturer:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Model:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Number of engines:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 9 * 16}, "Engine type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 11 * 16}, "Owner:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, "Operator:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 2 * 16}, "Registration:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Manufacturer:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 5 * 16}, "Model:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "Number of engines:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 9 * 16}, "Engine type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 11 * 16}, "Owner:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 13 * 16}, "Operator:", Theme::getInstance()->fg_light->foreground}};
Text text_icao_address{
{5 * 8, 1 * 16, 6 * 8, 16},
{5 * 8, 1 * 16, 6 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_registration{
{13 * 8, 2 * 16, 8 * 8, 16},
{13 * 8, 2 * 16, 8 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_manufacturer{
{0 * 8, 4 * 16, 19 * 8, 16},
{UI_POS_X(0), 4 * 16, 19 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_model{
{0 * 8, 6 * 16, screen_width, 16},
{UI_POS_X(0), 6 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_type{
{5 * 8, 7 * 16, 22 * 8, 16},
{5 * 8, 7 * 16, 22 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_number_of_engines{
{18 * 8, 8 * 16, screen_width, 16},
{18 * 8, 8 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_engine_type{
{0 * 8, 10 * 16, screen_width, 16},
{UI_POS_X(0), 10 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_owner{
{0 * 8, 12 * 16, screen_width, 16},
{UI_POS_X(0), 12 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_operator{
{0 * 8, 14 * 16, screen_width, 16},
{UI_POS_X(0), 14 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Button button_close{
{9 * 8, 16 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12), UI_POS_Y(16), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"Back"};
};
@@ -277,6 +278,13 @@ class ADSBRxDetailsView : public View {
std::string title() const override { return "Details"; }
MapType get_map_type() {
if (geomap_view_)
return geomap_view_->get_map_type();
else
return MapType::MAP_TYPE_BIN; // default
}
private:
void refresh_ui();
void on_gps(const GPSPosDataMessage* msg);
@@ -291,55 +299,55 @@ class ADSBRxDetailsView : public View {
bool airline_checked{false};
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{13 * 8, 1 * 16}, "Callsign:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Last seen:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Airline:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Country:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, "Even position frame:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 15 * 16}, "Odd position frame:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 2 * 16}, "Last seen:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Airline:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 5 * 16}, "Country:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 13 * 16}, "Even position frame:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 15 * 16}, "Odd position frame:", Theme::getInstance()->fg_light->foreground}};
Text text_icao_address{
{5 * 8, 1 * 16, 6 * 8, 16},
{5 * 8, 1 * 16, 6 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_callsign{
{22 * 8, 1 * 16, 8 * 8, 16},
{22 * 8, 1 * 16, 8 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_last_seen{
{11 * 8, 2 * 16, 19 * 8, 16},
{11 * 8, 2 * 16, 19 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_airline{
{0 * 8, 4 * 16, screen_width, 16},
{UI_POS_X(0), 4 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_country{
{8 * 8, 5 * 16, 22 * 8, 16},
{8 * 8, 5 * 16, 22 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_infos{
{0 * 8, 6 * 16, screen_width, 16},
{UI_POS_X(0), 6 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_info2{
{0 * 8, 7 * 16, screen_width, 16},
{UI_POS_X(0), 7 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_frame_pos_even{
{0 * 8, 14 * 16, screen_width, 16},
{UI_POS_X(0), 14 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_frame_pos_odd{
{0 * 8, 16 * 16, screen_width, 16},
{UI_POS_X(0), 16 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Button button_aircraft_details{
{2 * 8, 9 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) - UI_POS_X(8), UI_POS_Y(9), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"A/C details"};
Button button_see_map{
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) + UI_POS_X(8), UI_POS_Y(9), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See on map"};
MessageHandlerRegistration message_handler_gps{
@@ -388,15 +396,15 @@ class ADSBRxView : public View {
SignalToken signal_token_tick_second{};
uint8_t tick_count = 0;
uint16_t ticks_since_marker_refresh{MARKER_UPDATE_SECONDS};
uint16_t ticks_since_marker_refresh{MARKER_UPDATE_SECONDS_OSM};
/* Max number of entries that can be updated in a single pass.
* 16 is one screen of recent entries. */
static constexpr uint8_t max_update_entries = 16;
/* Recent Entries */
const RecentEntriesColumns columns{
{{"ICAO/Call", 9},
RecentEntriesColumns columns{
{{"ICAO/Call", 0},
{"Lvl", 3},
{"Spd", 3},
{"Amp", 3},
@@ -416,33 +424,33 @@ class ADSBRxView : public View {
ADSBRxDetailsView* details_view{nullptr};
Labels labels{
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
LNAGainField field_lna{
{4 * 8, 0 * 16}};
{UI_POS_X(4), UI_POS_Y(0)}};
VGAGainField field_vga{
{11 * 8, 0 * 16}};
{UI_POS_X(11), UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{20 * 8, 4, 7 * 8, 8},
{UI_POS_X(20), 4, UI_POS_WIDTH_REMAINING(23), 8},
};
ActivityDot status_frame{
{27 * 8 + 2, 5, 2, 2},
{UI_POS_X_RIGHT(3) + 2, 5, 2, 2},
Theme::getInstance()->bg_darkest->foreground,
};
ActivityDot status_good_frame{
{27 * 8 + 2, 9, 2, 2},
{UI_POS_X_RIGHT(3) + 2, 9, 2, 2},
Theme::getInstance()->fg_green->foreground,
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
MessageHandlerRegistration message_handler_frame{
Message::ID::ADSBFrame,
+6 -5
View File
@@ -42,7 +42,8 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
Color target_color;
// auto entry_age = entry.age;
@@ -51,8 +52,8 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
std::string entry_string = "";
entry_string += entry.source_formatted;
entry_string.append(10 - entry.source_formatted.size(), ' ');
entry_string += " ";
entry_string.resize(columns.at(0).second, ' ');
entry_string += " ";
entry_string += (entry.hits <= 999 ? to_string_dec_uint(entry.hits, 4) : "999+");
entry_string += " ";
entry_string += entry.time_string;
@@ -247,8 +248,8 @@ APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec)
details_view.hidden(true);
recent_entries_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
details_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
recent_entries_view.set_parent_rect({0, 0, screen_width, screen_height - 40});
details_view.set_parent_rect({0, 0, screen_width, screen_height - 40});
recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
this->on_show_detail(entry);
+18 -18
View File
@@ -134,14 +134,14 @@ class APRSDetailsView : public View {
bool send_updates{false};
Console console{
{0, 0 * 16, screen_width, 224}};
{UI_POS_X(0), UI_POS_Y(0), screen_width, screen_height - 80}};
Button button_done{
{160, 14 * 16, 8 * 8, 3 * 16},
{UI_POS_X_CENTER(4) - UI_POS_WIDTH(8), UI_POS_Y(14), UI_POS_WIDTH(8), UI_POS_HEIGHT(3)},
"Close"};
Button button_see_map{
{80, 14 * 16, 8 * 8, 3 * 16},
{UI_POS_X_CENTER(4) + UI_POS_WIDTH(8), UI_POS_Y(14), UI_POS_WIDTH(8), UI_POS_HEIGHT(3)},
"Map"};
};
@@ -161,10 +161,10 @@ class APRSTableView : public View {
private:
NavigationView& nav_;
const RecentEntriesColumns columns{{{"Source", 9},
{"Loc", 6},
{"Hits", 4},
{"Time", 8}}};
RecentEntriesColumns columns{{{"Source", 0},
{"Loc", 6},
{"Hits", 4},
{"Time", 8}}};
APRSRecentEntries recent{};
RecentEntriesView<RecentEntries<APRSRecentEntry>> recent_entries_view{columns, recent};
APRSDetailsView details_view{nav_};
@@ -209,21 +209,21 @@ class APRSRxView : public View {
std::string str_log{""};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
OptionsField options_region{
{0 * 8, 0 * 8},
{UI_POS_X(0), UI_POS_Y(0)},
3,
{{"MAN", 0},
{"NA ", 1},
@@ -237,11 +237,11 @@ class APRSRxView : public View {
{"ISS", 9}}};
FrequencyField field_frequency{
{3 * 8, 0 * 16}};
{UI_POS_X(3), UI_POS_Y(0)}};
// DEBUG
RecordView record_view{
{0 * 8, 1 * 16, screen_width, 1 * 16},
{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
u"AFS_????.WAV",
aprs_dir,
RecordView::FileType::WAV,
@@ -249,7 +249,7 @@ class APRSRxView : public View {
4};
Console console{
{0, 2 * 16, screen_width, screen_height - 80}};
{UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, screen_height - 80}};
std::unique_ptr<APRSLogger> logger{};
};
@@ -265,7 +265,7 @@ class APRSRXView : public View {
private:
NavigationView& nav_;
Rect view_rect = {0, 3 * 8, screen_width, screen_height - 40};
Rect view_rect = {UI_POS_X(0), 3 * 8, UI_POS_MAXWIDTH, screen_height - 40};
APRSRxView view_stream{nav_, view_rect};
APRSTableView view_table{nav_, view_rect};
+124 -1
View File
@@ -29,6 +29,7 @@
#include "baseband_api.hpp"
#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_geomap.hpp"
#include <cstring>
#include <stdio.h>
@@ -49,10 +50,24 @@ APRSTXView::~APRSTXView() {
void APRSTXView::start_tx() {
// TODO: Clean up this API to take string_views to avoid allocations.
std::string new_payload = payload;
std::string gps = text_gps_coord.get();
std::string token = "?GPS?";
size_t pos = 0;
while ((pos = new_payload.find(token, pos)) != std::string::npos) {
new_payload.replace(pos, token.size(), gps);
pos += gps.size();
}
text_payload.set(new_payload);
std::string path = text_path.get();
make_aprs_frame(
sym_source.to_string().c_str(), num_ssid_source.value(),
sym_dest.to_string().c_str(), num_ssid_dest.value(),
payload);
new_payload, path);
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
// text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
@@ -77,6 +92,61 @@ void APRSTXView::on_tx_progress(const uint32_t progress, const bool done) {
}
}
void APRSTXView::process_coordinates(float lat_, float lon_) {
std::string s;
char ns = lat_ >= 0 ? 'N' : 'S';
float lat = (lat_ >= 0) ? lat_ : -lat_;
uint32_t lat_d = (uint32_t)lat;
uint32_t lat_m = (uint32_t)((lat - lat_d) * 6000.0f + 0.5f);
if (lat_m >= 6000) {
lat_m = 0;
lat_d++;
}
s += to_string_dec_uint(lat_d, 2, '0');
s += to_string_dec_uint(lat_m / 100, 2, '0');
s += ".";
s += to_string_dec_uint(lat_m % 100, 2, '0');
s += ns;
s += "/";
char ew = lon_ >= 0 ? 'E' : 'W';
float lon = (lon_ >= 0) ? lon_ : -lon_;
uint32_t lon_d = (uint32_t)lon;
uint32_t lon_m = (uint32_t)((lon - lon_d) * 6000.0f + 0.5f);
if (lon_m >= 6000) {
lon_m = 0;
lon_d++;
}
s += to_string_dec_uint(lon_d, 3, '0');
s += to_string_dec_uint(lon_m / 100, 2, '0');
s += ".";
s += to_string_dec_uint(lon_m % 100, 2, '0');
s += ew;
text_gps_coord.set(s);
}
void APRSTXView::on_gps(const GPSPosDataMessage* message) {
if (manual_gps_mode) {
return; // no more update once set manually
}
if (message->lat < -90.0 || message->lat > 90.0 ||
message->lon < -180.0 || message->lon > 180.0) {
return;
}
if (message->lat == 0.0f && message->lon == 0.0f) {
return;
}
last_lat = message->lat;
last_lon = message->lon;
process_coordinates(message->lat, message->lon);
}
APRSTXView::APRSTXView(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_afsk);
@@ -87,8 +157,33 @@ APRSTXView::APRSTXView(NavigationView& nav) {
&num_ssid_dest,
&text_payload,
&button_set,
&text_gps_coord,
&button_mangps,
&gps_is_manual,
&text_path,
&button_setpath,
&tx_view});
sym_source.set_value(symsrc);
num_ssid_source.set_value(ssidsrc);
sym_dest.set_value(symdst);
num_ssid_dest.set_value(ssiddst);
text_payload.set(payload);
text_path.set(path_cache);
sym_source.on_change = [this](SymField&) {
symsrc = sym_source.to_string();
};
num_ssid_source.on_change = [this](int32_t v) {
ssidsrc = v;
};
sym_dest.on_change = [this](SymField&) {
symdst = sym_dest.to_string();
};
num_ssid_dest.on_change = [this](int32_t v) {
ssiddst = v;
};
button_set.on_select = [this, &nav](Button&) {
text_prompt(
nav,
@@ -99,6 +194,17 @@ APRSTXView::APRSTXView(NavigationView& nav) {
text_payload.set(s);
});
};
button_setpath.on_select = [this, &nav](Button&) {
text_prompt(
nav,
path_cache,
60,
ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& s) {
text_path.set(s);
path_cache = s;
});
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
@@ -116,6 +222,23 @@ APRSTXView::APRSTXView(NavigationView& nav) {
tx_view.set_transmitting(false);
transmitter_model.disable();
};
button_mangps.on_select = [this, &nav](Button&) {
nav.push<GeoMapView>(
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN,
last_lat,
last_lon,
[this](int32_t, float lat, float lon, int32_t) {
last_lat = lat;
last_lon = lon;
manual_gps_mode = true;
gps_is_manual.set("MANUAL");
process_coordinates(lat, lon);
});
};
// process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users
}
} /* namespace ui */
+63 -13
View File
@@ -50,59 +50,109 @@ class APRSTXView : public View {
1750000 /* bandwidth */,
AFSK_TX_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_aprs", app_settings::Mode::TX};
std::string symsrc = "";
std::string symdst = "";
std::string payload{""};
std::string path_cache = "";
int32_t ssidsrc = 0;
int32_t ssiddst = 0;
bool manual_gps_mode = false;
float last_lat = 0.0f;
float last_lon = 0.0f;
app_settings::SettingsManager settings_{
"tx_aprs",
app_settings::Mode::TX,
{{"symsrc"sv, &symsrc},
{"symdst"sv, &symdst},
{"ssidsrc"sv, &ssidsrc},
{"ssiddst"sv, &ssiddst},
{"payload"sv, &payload},
{"last_lat"sv, &last_lat},
{"last_lon"sv, &last_lon},
{"path"sv, &path_cache}}};
void start_tx();
void generate_frame();
void generate_frame_pos();
void on_tx_progress(const uint32_t progress, const bool done);
void on_gps(const GPSPosDataMessage* message);
void process_coordinates(float lat, float lon);
Labels labels{
{{0 * 8, 1 * 16}, "Source: SSID:", Theme::getInstance()->fg_light->foreground}, // 6 alphanum + SSID
{{0 * 8, 2 * 16}, " Dest.: SSID:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Info field:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Source: SSID:", Theme::getInstance()->fg_light->foreground}, // 6 alphanum + SSID
{{UI_POS_X(0), UI_POS_Y(1)}, " Dest.: SSID:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(2)}, "Path: (ex.: WIDE1-1,WIDE2-1)", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(6)}, "Info field:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(10)}, "GPS:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(22), UI_POS_Y(14)}, "Use ?GPS? in the info", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(17), UI_POS_Y(15)}, "as a placeholder.", Theme::getInstance()->fg_light->foreground},
};
Text text_path{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
"WIDE1-1",
};
Text gps_is_manual{
{UI_POS_X(6), UI_POS_Y(10), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"",
};
SymField sym_source{
{7 * 8, 1 * 16},
{UI_POS_X(7), UI_POS_Y(0)},
6,
SymField::Type::Alpha};
NumberField num_ssid_source{
{19 * 8, 1 * 16},
{UI_POS_X(19), UI_POS_Y(0)},
2,
{0, 15},
1,
' '};
SymField sym_dest{
{7 * 8, 2 * 16},
{UI_POS_X(7), UI_POS_Y(1)},
6,
SymField::Type::Alpha};
NumberField num_ssid_dest{
{19 * 8, 2 * 16},
{UI_POS_X(19), UI_POS_Y(1)},
2,
{0, 15},
1,
' '};
Text text_payload{
{0 * 8, 5 * 16, screen_width, 16},
"-"};
{UI_POS_X(0), UI_POS_Y(7), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
""};
Button button_set{
{0 * 8, 6 * 16, 80, 32},
{UI_POS_X(0), UI_POS_Y(8), UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
"Set"};
Text text_gps_coord{
{UI_POS_X(0), UI_POS_Y(11), UI_POS_WIDTH(20), UI_POS_HEIGHT(1)},
"-"};
Button button_mangps{
{UI_POS_X(0), UI_POS_Y(12), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Manual GPS"};
Button button_setpath{
{UI_POS_X(0), UI_POS_Y(4), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Set Path"};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
5000,
0 // disable setting bandwith, since APRS used fixed 10k bandwidth
};
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
[this](Message* const p) {
const auto message = static_cast<const GPSPosDataMessage*>(p);
this->on_gps(message);
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
+1 -1
View File
@@ -98,7 +98,7 @@ class BattinfoView : public View {
"Volt"};
Button button_exit{
{72, 17 * 16, 96, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Back"};
static msg_t static_fn(void* arg);
Thread* thread{nullptr};
+7 -7
View File
@@ -63,22 +63,22 @@ class BTLERxView : public View {
static constexpr uint8_t channel_number = 37;
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(21), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21), 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
Button button_modem_setup{
{screen_width - 12 * 8, 1 * 16, 96, 24},
{screen_width - 12 * 8, UI_POS_Y(1), 96, 24},
"Modem setup"};
Console console{
+12 -12
View File
@@ -242,9 +242,9 @@ class DebugControlsView : public View {
private:
Labels labels{
{{8 * 8, 1 * 16}, "Controls State", Theme::getInstance()->bg_darkest->foreground},
{{0 * 8, 11 * 16}, "Dial:", Theme::getInstance()->fg_medium->foreground},
{{0 * 8, 14 * 16}, "Long-Press Mode:", Theme::getInstance()->fg_medium->foreground}};
{{UI_POS_X_CENTER(14), 1 * 16}, "Controls State", Theme::getInstance()->bg_darkest->foreground},
{{UI_POS_X(0), 11 * 16}, "Dial:", Theme::getInstance()->fg_medium->foreground},
{{UI_POS_X(0), 14 * 16}, "Long-Press Mode:", Theme::getInstance()->fg_medium->foreground}};
ControlsSwitchesWidget switches_widget{
{80, 80, 80, 112},
@@ -259,7 +259,7 @@ class DebugControlsView : public View {
}};
Button button_done{
{72, 264, 96, 24},
{UI_POS_X_CENTER(12), 264, 96, 24},
"Done"};
};
@@ -271,28 +271,28 @@ class DebugMemoryDumpView : public View {
private:
Button button_dump{
{72, 4 * 16, 96, 24},
{UI_POS_X_CENTER(12), 4 * 16, 96, 24},
"Dump"};
Button button_read{
{16, 11 * 16, 96, 24},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(10), 11 * 16, 96, 24},
"Read"};
Button button_write{
{128, 11 * 16, 96, 24},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(10), 11 * 16, 96, 24},
"Write"};
Button button_done{
{128, screen_height - 80, 96, 24},
{UI_POS_X_RIGHT(12), UI_POS_Y_BOTTOM(3), 96, 24},
"Done"};
Labels labels{
{{5 * 8, 1 * 16}, "Dump Range to File", Theme::getInstance()->fg_yellow->foreground},
{{0 * 8, 2 * 16}, "Starting Address: 0x", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Byte Count: 0x", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 2 * 16}, "Starting Address: 0x", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Byte Count: 0x", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 8 * 16}, "Read/Write Single Word", Theme::getInstance()->fg_yellow->foreground},
{{0 * 8, 9 * 16}, "Memory Address: 0x", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 10 * 16}, "Data Value: 0x", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 9 * 16}, "Memory Address: 0x", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 10 * 16}, "Data Value: 0x", Theme::getInstance()->fg_light->foreground}};
SymField field_starting_address{
{20 * 8, 2 * 16},
@@ -12,7 +12,7 @@ BatteryCapacityView::RegisterEntry BatteryCapacityView::get_entry(size_t index)
BatteryCapacityView::BatteryCapacityView(NavigationView& nav) {
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
name_texts[i].set_parent_rect({0 * 8, static_cast<int>((i + 1) * 16), 8 * 8, 16});
name_texts[i].set_parent_rect({UI_POS_X(0), static_cast<int>((i + 1) * 16), 8 * 8, 16});
addr_texts[i].set_parent_rect({9 * 8, static_cast<int>((i + 1) * 16), 4 * 8, 16});
hex_texts[i].set_parent_rect({14 * 8, static_cast<int>((i + 1) * 16), 6 * 8, 16});
value_texts[i].set_parent_rect({21 * 8, static_cast<int>((i + 1) * 16), 10 * 8, 16});
@@ -24,18 +24,18 @@ class BatteryCapacityView : public View {
static RegisterEntry get_entry(size_t index);
Labels labels{
{{0 * 8, 0 * 16}, "Reg", Theme::getInstance()->fg_yellow->foreground},
{{9 * 8, 0 * 16}, "Addr", Theme::getInstance()->fg_yellow->foreground},
{{14 * 8, 0 * 16}, "Hex", Theme::getInstance()->fg_yellow->foreground},
{{21 * 8, 0 * 16}, "Value", Theme::getInstance()->fg_yellow->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Reg", Theme::getInstance()->fg_yellow->foreground},
{{9 * 8, UI_POS_Y(0)}, "Addr", Theme::getInstance()->fg_yellow->foreground},
{{14 * 8, UI_POS_Y(0)}, "Hex", Theme::getInstance()->fg_yellow->foreground},
{{21 * 8, UI_POS_Y(0)}, "Value", Theme::getInstance()->fg_yellow->foreground},
};
std::array<Text, 16> name_texts = {};
std::array<Text, 16> addr_texts = {};
std::array<Text, 16> hex_texts = {};
std::array<Text, 16> value_texts = {};
Text page_text{{144, 284, 80, 16}, "Page 1/1"};
Button button_done{{16, 280, 96, 24}, "Done"};
Text page_text{{144, UI_POS_Y_BOTTOM(3), 80, 16}, "Page 1/1"};
Button button_done{{16, UI_POS_Y_BOTTOM(3), 96, 24}, "Done"};
void update_values();
void populate_page(int start_index);
+2 -2
View File
@@ -216,10 +216,10 @@ class EncodersView : public View {
"Ready"};
ProgressBar progressbar{
{2 * 8, 13 * 16 + 20, 208, 16}};
{2 * 8, 13 * 16 + 20, UI_POS_WIDTH_REMAINING(4), 16}};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
50000,
9};
+5 -4
View File
@@ -294,6 +294,7 @@ FileManBaseView::FileManBaseView(
std::string filter)
: nav_{nav},
extension_filter{filter} {
max_filename_length = screen_width / 8 - 10;
add_children({&labels,
&text_current,
&button_exit});
@@ -403,7 +404,7 @@ void FileManBaseView::refresh_list() {
menu_view.clear();
for (const auto& entry : entry_list) {
auto entry_name = std::string{entry.path.length() <= 20 ? entry.path : entry.path.substr(0, 20)};
auto entry_name = std::string{entry.path.length() <= max_filename_length ? entry.path : entry.path.substr(0, max_filename_length)};
if (entry.is_directory) {
std::string size_str{};
@@ -414,7 +415,7 @@ void FileManBaseView::refresh_list() {
}
menu_view.add_item(
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
{entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str,
Theme::getInstance()->fg_yellow->foreground,
&bitmap_icon_dir,
[this](KeyEvent key) {
@@ -427,7 +428,7 @@ void FileManBaseView::refresh_list() {
auto size_str = to_string_file_size(entry.size);
menu_view.add_item(
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
{entry_name.substr(0, max_filename_length) + std::string((max_filename_length + 1) - entry_name.length(), ' ') + size_str,
assoc.color,
assoc.icon,
[this](KeyEvent key) {
@@ -486,7 +487,7 @@ FileLoadView::FileLoadView(
add_children({&menu_view});
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, screen_width, 29 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(4.5)});
refresh_list();
+17 -17
View File
@@ -65,7 +65,7 @@ class FileManBaseView : public View {
uint8_t pagination = 0;
uint8_t nb_pages = 1;
bool restoring_navigation = false;
static constexpr size_t max_filename_length = 20;
size_t max_filename_length = 20;
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
static constexpr size_t items_per_page = 20;
@@ -122,16 +122,16 @@ class FileManBaseView : public View {
{{0, 0}, "Path:", Theme::getInstance()->fg_light->foreground}};
Text text_current{
{6 * 8, 0 * 8, 24 * 8, 16},
{UI_POS_X(6), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(6), UI_POS_HEIGHT(1)},
"",
};
MenuView menu_view{
{0, 2 * 8, screen_width, 26 * 8},
{0, UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT_REMAINING(7)},
true};
Button button_exit{
{22 * 8, 34 * 8, 8 * 8, 32},
{UI_POS_X_RIGHT(8), UI_POS_Y_BOTTOM(3), 8 * 8, UI_POS_HEIGHT(2)},
"Exit"};
};
@@ -230,66 +230,66 @@ class FileManagerView : public FileManBaseView {
bool selected_is_valid() const;
Text text_date{
{0 * 8, 26 * 8, 28 * 8, 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(6), 28 * 8, UI_POS_HEIGHT(1)},
""};
NewButton button_rename{
{0 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(0), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_rename,
Theme::getInstance()->fg_blue->foreground};
NewButton button_delete{
{9 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(9), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_trash,
Theme::getInstance()->fg_red->foreground};
NewButton button_clean{
{13 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(13), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_clean,
Theme::getInstance()->fg_red->foreground};
NewButton button_cut{
{9 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(9), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_cut,
Theme::getInstance()->fg_dark->foreground};
NewButton button_copy{
{13 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(13), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_copy,
Theme::getInstance()->fg_dark->foreground};
NewButton button_paste{
{17 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(17), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_paste,
Theme::getInstance()->fg_dark->foreground};
NewButton button_new_dir{
{22 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(22), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_new_dir,
Theme::getInstance()->fg_green->foreground};
NewButton button_new_file{
{26 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(26), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_new_file,
Theme::getInstance()->fg_green->foreground};
NewButton button_open_notepad{
{0 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_notepad,
Theme::getInstance()->fg_orange->foreground};
NewButton button_rename_timestamp{
{4 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(4), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_options_datetime,
Theme::getInstance()->fg_blue->foreground,
@@ -297,13 +297,13 @@ class FileManagerView : public FileManBaseView {
NewButton button_open_iq_trim{
{4 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(4), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_trim,
Theme::getInstance()->fg_orange->foreground};
NewButton button_show_hidden_files{
{17 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(17), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_hide,
Theme::getInstance()->fg_dark->foreground};
+13 -2
View File
@@ -45,10 +45,15 @@ bool valid_firmware_file(std::filesystem::path::string_type path) {
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
auto result = firmware_file.open(path.c_str());
if (!result.is_valid()) {
uint64_t file_size = firmware_file.size();
if (portapack::device_type == portapack::DeviceType::DEV_PORTAPACK && file_size > 1 * 1024 * 1024) {
// Portapack firmware files must not be larger than 1MB
return false;
}
// May need to add a check to portarf and portarf pro too.
checksum = 0;
for (uint32_t offset = 0; offset < FLASH_ROM_SIZE; offset += sizeof(read_buffer)) {
for (uint64_t offset = 0; offset < FLASH_ROM_SIZE && offset < file_size; offset += sizeof(read_buffer)) {
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
// File was smaller than 1MB:
// If version is such that the file SHOULD have been 1MB, call it a checksum error (otherwise say it's OK).
@@ -130,6 +135,12 @@ bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string&
}
}
void FlashUtilityView::wait_till_loaded() {
while (!isLoaded) {
chThdSleepMilliseconds(50);
}
}
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
//
painter.fill_rectangle(
+13 -2
View File
@@ -32,7 +32,9 @@
#include "untar.hpp"
#include <cstdint>
#define FLASH_ROM_SIZE 1048576
#include "../../flashsize.h"
#define FLASH_ROM_SIZE (FLASH_SIZE_MB * 1024 * 1024)
#define FLASH_STARTING_ADDRESS 0x00000000
#define FLASH_EXPECTED_CHECKSUM 0x00000000
#define FLASH_CHECKSUM_ERROR 0xFFFFFFFF
@@ -49,6 +51,7 @@ class FlashUtilityView : public View {
std::string title() const override { return "Flash Utility"; };
bool flash_firmware(std::filesystem::path::string_type path);
void wait_till_loaded();
private:
NavigationView& nav_;
@@ -60,13 +63,21 @@ class FlashUtilityView : public View {
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, screen_width, 26 * 8},
{0, UI_POS_Y(1), screen_width, UI_POS_HEIGHT_REMAINING(2)},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
void firmware_selected(std::filesystem::path::string_type path);
bool endsWith(const std::u16string& str, const std::u16string& suffix);
bool isLoaded = false;
uint8_t refreshcnt = 0;
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
refreshcnt++;
if (refreshcnt > 5) isLoaded = true;
}};
};
} /* namespace ui */
+23 -23
View File
@@ -65,14 +65,14 @@ class FreqManBaseView : public View {
OptionsField options_category{
{3 * 8, 2},
20 /* length */,
19 /* length */,
{}};
FreqManUIList freqlist_view{
{0, 3 * 8, screen_width, 12 * 16 + 2 /* 2 Keeps text out of border. */}};
Button button_exit{
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X_RIGHT(14), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Exit"};
protected:
@@ -97,14 +97,14 @@ class FrequencySaveView : public FreqManBaseView {
0};
Labels labels{
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
{{UI_POS_X(0), 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
TextField field_description{
{0 * 8, 7 * 16, screen_width, 1 * 16},
{UI_POS_X(0), 7 * 16, screen_width, 1 * 16},
""};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Save"};
};
@@ -134,44 +134,44 @@ class FrequencyManagerView : public FreqManBaseView {
void on_del_entry();
NewButton button_add_category{
{23 * 8, 0 * 16, 7 * 4, 20},
{UI_POS_X_RIGHT(8), UI_POS_Y(0), 7 * 4, 20},
{},
&bitmap_icon_new_file,
Theme::getInstance()->bg_darkest->foreground,
true};
NewButton button_del_category{
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
{UI_POS_X_RIGHT(4), UI_POS_Y(0), 7 * 4, 20},
{},
&bitmap_icon_trash,
Theme::getInstance()->fg_red->foreground,
true};
Button button_edit_entry{
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
{UI_POS_X(0), UI_POS_Y_BOTTOM(6.5) + 4, 15 * 8, 1 * 16 + 4},
"Edit"};
Rectangle rect_padding{
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
{15 * 8, UI_POS_Y_BOTTOM(6.5), 15 * 8, 1 * 16 + 4},
Theme::getInstance()->fg_medium->background};
Button button_edit_freq{
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(5), 15 * 8, 2 * 16},
"Frequency"};
Button button_edit_desc{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
"Description"};
NewButton button_add_entry{
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
{15 * 8, UI_POS_Y_BOTTOM(5), 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_add,
Theme::getInstance()->bg_darkest->foreground,
true};
NewButton button_del_entry{
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
{22 * 8 + 4, UI_POS_Y_BOTTOM(5), 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_delete,
Theme::getInstance()->fg_red->foreground,
@@ -201,14 +201,14 @@ class FrequencyEditView : public View {
Labels labels{
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Theme::getInstance()->bg_darkest->foreground},
{{0 * 8, 3 * 16}, "Entry Type :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Frequency A:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Frequency B:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Modulation :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Bandwidth :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Step :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 9 * 16}, "Tone Freq :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 10 * 16}, "Description:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Entry Type :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 4 * 16}, "Frequency A:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 5 * 16}, "Frequency B:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 6 * 16}, "Modulation :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "Bandwidth :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "Step :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 9 * 16}, "Tone Freq :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 10 * 16}, "Description:", Theme::getInstance()->fg_light->foreground},
};
OptionsField field_type{
@@ -243,11 +243,11 @@ class FrequencyEditView : public View {
{}};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
"Save"};
Button button_cancel{
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X_RIGHT(15), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
"Cancel"};
};
+1 -1
View File
@@ -171,7 +171,7 @@ void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
}
void IQTrimView::profile_capture() {
power_buckets_ = {};
power_buckets_.clear();
iq::PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
+11 -11
View File
@@ -54,7 +54,7 @@ class TrimProgressUI {
}
void clear() {
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Theme::getInstance()->bg_darkest->background);
p.fill_rectangle({UI_POS_X(0), 4 * 16, screen_width, 3 * 16}, Theme::getInstance()->bg_darkest->background);
}
auto get_callback() {
@@ -100,22 +100,22 @@ class IQTrimView : public View {
TrimProgressUI progress_ui{};
Labels labels{
{{0 * 8, 0 * 16}, "Capture File:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Start :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "End :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Samples:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 9 * 16}, "Max Pwr:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 10 * 16}, "Cutoff :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Capture File:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 6 * 16}, "Start :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "End :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "Samples:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 9 * 16}, "Max Pwr:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 10 * 16}, "Cutoff :", Theme::getInstance()->fg_light->foreground},
{{12 * 8, 10 * 16}, "%", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 12 * 16}, "Amplify:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 12 * 16}, "Amplify:", Theme::getInstance()->fg_light->foreground},
{{10 * 8, 12 * 16}, "x", Theme::getInstance()->fg_light->foreground},
};
TextField field_path{
{0 * 8, 1 * 16, screen_width, 1 * 16},
{UI_POS_X(0), 1 * 16, screen_width, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
Point pos_lines{UI_POS_X(0), 4 * 16};
Dim height_lines{2 * 16};
NumberField field_start{
@@ -155,7 +155,7 @@ class IQTrimView : public View {
' '};
Button button_trim{
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
{UI_POS_X_CENTER(8), UI_POS_Y_BOTTOM(3), 8 * 8, 2 * 16},
"Trim"};
};
@@ -69,7 +69,7 @@ void GlassView::manage_beep_audio() {
}
}
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power) {
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint16_t bin, uint8_t& max_power) {
if (mode == LOOKING_GLASS_SINGLEPASS) {
// <20MHz spectrum mode
if (bin < 120) {
@@ -91,7 +91,7 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
}
}
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency GlassView::get_freq_from_bin_pos(uint16_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by screen_width(240)/2 => 120
@@ -101,7 +101,14 @@ rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
return freq_at_pos;
}
void GlassView::on_marker_change() {
void GlassView::on_marker_change(int8_t delta) {
if ((marker_pixel_index + delta) < 0)
marker_pixel_index = marker_pixel_index + delta + screen_width;
else if ((marker_pixel_index + delta) > screen_width)
marker_pixel_index = marker_pixel_index + delta - screen_width;
else
marker_pixel_index = marker_pixel_index + delta;
marker = get_freq_from_bin_pos(marker_pixel_index);
field_marker.set_text(to_string_short_freq(marker));
plot_marker(marker_pixel_index); // Refresh marker on screen
@@ -200,7 +207,7 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
// we actually need screen_width (240) of those bins
for (uint8_t bin = 0; bin < bin_length; bin++) {
for (uint16_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (max_power > range_max_power)
range_max_power = max_power;
@@ -208,7 +215,7 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
if (bin == 119) {
uint8_t next_max_power = 0;
get_max_power(spectrum, bin + 1, next_max_power);
for (uint8_t it = 0; it < ignore_dc; it++) {
for (uint16_t it = 0; it < ignore_dc; it++) {
uint8_t med_max_power = (max_power + next_max_power) / 2; // due to the way process_bins works we have to keep resetting the color
if (process_bins(&med_max_power) == true)
return; // new line signaled, return
@@ -285,7 +292,7 @@ void GlassView::on_range_changed() {
max_power = 0;
bins_hz_size = 0;
on_marker_change();
on_marker_change(0);
update_range_field();
// set the sample rate and bandwidth
@@ -298,8 +305,8 @@ void GlassView::on_range_changed() {
receiver_model.set_target_frequency(f_center); // tune rx for this slice
}
void GlassView::plot_marker(uint8_t pos) {
uint8_t shift_y = 0;
void GlassView::plot_marker(uint16_t pos) {
uint16_t shift_y = 0;
if (live_frequency_view > 0) // plot one line down when in live view
{
shift_y = 16;
@@ -381,9 +388,10 @@ GlassView::GlassView(
&button_beep_squelch,
&field_marker,
&field_trigger,
&button_marker_minus,
&button_marker_plus,
&button_jump,
&button_rst,
&field_rx_iq_phase_cal,
&freq_stats});
load_presets(); // Load available presets from TXT files (or default).
@@ -491,13 +499,7 @@ GlassView::GlassView(
range_presets.set_selected_index(preset_index);
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
if ((marker_pixel_index + delta) < 0)
marker_pixel_index = marker_pixel_index + delta + screen_width;
else if ((marker_pixel_index + delta) > screen_width)
marker_pixel_index = marker_pixel_index + delta - screen_width;
else
marker_pixel_index = marker_pixel_index + delta;
on_marker_change();
on_marker_change(delta);
};
field_marker.on_select = [this](TextField&) {
@@ -516,6 +518,14 @@ GlassView::GlassView(
update_range_field();
};
button_marker_minus.on_select = [this](Button&) {
on_marker_change(-1);
};
button_marker_plus.on_select = [this](Button&) {
on_marker_change(1);
};
button_jump.on_select = [this](Button&) {
// Launch Audio with peak frequency.
launch_audio(max_freq_hold);
@@ -525,13 +535,6 @@ GlassView::GlassView(
reset_live_view();
};
field_rx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
field_rx_iq_phase_cal.set_value(get_spec_iq_phase_calibration_value()); // using accessor function of AnalogAudioView to read iq_phase_calibration_value from rx_audio.ini
field_rx_iq_phase_cal.on_change = [this](int32_t v) {
set_spec_iq_phase_calibration_value(v); // using accessor function of AnalogAudioView to write inside SPEC submenu, register value to max283x and save it to rx_audio.ini
};
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
// trigger:
@@ -581,15 +584,6 @@ void GlassView::on_freqchg(int64_t freq) {
on_range_changed();
}
uint8_t GlassView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
return iq_phase_calibration_value;
}
void GlassView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
iq_phase_calibration_value = cal_value;
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
}
void GlassView::load_presets() {
File presets_file;
auto error = presets_file.open(looking_glass_dir / u"PRESETS.TXT");
@@ -66,9 +66,6 @@ class GlassView : public View {
void on_hide() override;
void focus() override;
uint8_t get_spec_iq_phase_calibration_value();
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
private:
NavigationView& nav_;
Gradient gradient{};
@@ -80,11 +77,10 @@ class GlassView : public View {
uint8_t filter_index = 0; // OFF
uint8_t trigger = 32;
uint8_t mode = LOOKING_GLASS_FASTSCAN;
uint8_t live_frequency_view = 0; // Spectrum
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
int32_t beep_squelch = 20; // range from -100 to +20, >=20 disabled
bool beep_enabled = false; // activate on bip button click
uint8_t live_frequency_view = 0; // Spectrum
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
int32_t beep_squelch = 20; // range from -100 to +20, >=20 disabled
bool beep_enabled = false; // activate on bip button click
app_settings::SettingsManager settings_{
"rx_glass"sv,
app_settings::Mode::RX,
@@ -97,7 +93,6 @@ class GlassView : public View {
{"scan_mode"sv, &mode},
{"freq_view"sv, &live_frequency_view},
{"freq_integrate"sv, &live_frequency_integrate},
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
{"beep_squelch"sv, &beep_squelch},
{"beep_enabled"sv, &beep_enabled},
}};
@@ -116,9 +111,9 @@ class GlassView : public View {
void update_min(int32_t v);
void update_max(int32_t v);
void update_range_field();
void get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint8_t pos);
void on_marker_change();
void get_max_power(const ChannelSpectrum& spectrum, uint16_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint16_t pos);
void on_marker_change(int8_t delta);
void retune();
bool process_bins(uint8_t* powerlevel);
void on_channel_spectrum(const ChannelSpectrum& spectrum);
@@ -128,7 +123,7 @@ class GlassView : public View {
void on_range_changed();
void reset_live_view();
void add_spectrum_pixel(uint8_t power);
void plot_marker(uint8_t pos);
void plot_marker(uint16_t pos);
void load_presets();
void populate_presets();
void launch_audio(rf::Frequency center_freq);
@@ -138,7 +133,7 @@ class GlassView : public View {
rf::Frequency f_center_ini{0};
rf::Frequency marker{0};
uint8_t marker_pixel_index{0};
uint16_t marker_pixel_index{0};
rf::Frequency marker_pixel_step{0};
// Size of one spectrum bin in Hz.
@@ -170,32 +165,31 @@ class GlassView : public View {
uint8_t ignore_dc = 0;
Labels labels{
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
{{0, UI_POS_Y(0)}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
{{0, 2 * 16}, "P:", Theme::getInstance()->fg_light->foreground},
{{0, 3 * 16}, "MARKER: MHz RXIQCAL", Theme::getInstance()->fg_light->foreground},
//{{0, 4 * 16}, "RES: STEPS:", Theme::getInstance()->fg_light->foreground}};
{{0, 3 * 16}, "MARKER( / ): MHz", Theme::getInstance()->fg_light->foreground},
{{0, 4 * 16}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
NumberField field_frequency_min{
{4 * 8, 0 * 16},
{4 * 8, UI_POS_Y(0)},
4,
{0, 7199},
1, // number of steps by encoder delta
' '};
NumberField field_frequency_max{
{13 * 8, 0 * 16},
{13 * 8, UI_POS_Y(0)},
4,
{1, 7200},
1, // number of steps by encoder delta
' '};
LNAGainField field_lna{
{21 * 8, 0 * 16}};
{21 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{27 * 8, 0 * 16}};
{27 * 8, UI_POS_Y(0)}};
TextField field_range{
{6 * 8, 1 * 16, 6 * 8, 16},
@@ -223,16 +217,16 @@ class GlassView : public View {
""};
TextField field_marker{
{7 * 8, 3 * 16, 9 * 8, 16},
{12 * 8, 3 * 16, 9 * 8, 16},
""};
NumberField field_rx_iq_phase_cal{
{28 * 8, 3 * 16},
2,
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
1,
' ',
};
Button button_marker_minus{
{7 * 8, 3 * 16 + 4, 1 * 8, 1 * 8},
"-"};
Button button_marker_plus{
{9 * 8, 3 * 16 + 4, 1 * 8, 1 * 8},
"+"};
NumberField field_trigger{
{4 * 8, 4 * 16},
@@ -298,7 +292,7 @@ class GlassView : public View {
"RST"};
Text freq_stats{
{0 * 8, 5 * 16, screen_width - 10 * 8, 8},
{UI_POS_X(0), 5 * 16, screen_width - 10 * 8, 8},
""};
MessageHandlerRegistration message_handler_spectrum_config{
+2
View File
@@ -446,6 +446,8 @@ MicTXView::MicTXView(
field_frequency.set_value(tx_frequency);
// TODO: would be nice if frequency step was configurable in this app
// but now, make it a bit lower
receiver_model.set_frequency_step(12'500); // set freq step to 12.5kHz
field_frequency.set_step(receiver_model.frequency_step());
// WARNING: transmitter_model.set_target_frequency() and receiver_model.set_target_frequency() both update the same tuning freq, but one has an offset!
+1 -1
View File
@@ -170,7 +170,7 @@ class MicTXView : public View {
{{17 * 8, 1 * 8}, "ALC", Theme::getInstance()->fg_light->foreground}};
VuMeter vumeter{
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
{UI_POS_X(0), 1 * 8, 2 * 8, 33 * 8},
12,
true};
+1 -1
View File
@@ -104,7 +104,7 @@ class ModemSetupView : public View {
"SET"};
Button button_save{
{9 * 8, 250, 96, 40},
{UI_POS_X_CENTER(12), 250, UI_POS_WIDTH(12), 40},
"Save"};
};
@@ -387,7 +387,6 @@ PlaylistView::PlaylistView(
&button_next,
&waterfall,
});
ensure_directory(playlist_dir);
waterfall.show_audio_spectrum_view(false);
+7 -7
View File
@@ -107,24 +107,24 @@ class PlaylistView : public View {
void handle_replay_thread_done(uint32_t return_code);
Text text_filename{
{0 * 8, 0 * 16, screen_width, 16}};
{UI_POS_X(0), UI_POS_Y(0), screen_width, 16}};
FrequencyField field_frequency{
{0 * 8, 1 * 16}};
{UI_POS_X(0), 1 * 16}};
Text text_sample_rate{
{10 * 8, 1 * 16, 7 * 8, 16}};
ProgressBar progressbar_track{
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
{18 * 8, 1 * 16, UI_POS_WIDTH_REMAINING(19), 8 + 1}};
// (-1) to overlap with progressbar_track so there's
// only 1 pixel between them instead of 2.
ProgressBar progressbar_transmit{
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
{18 * 8, 3 * 8 - 1, UI_POS_WIDTH_REMAINING(19), 8}};
Text text_duration{
{0 * 8, 2 * 16, 5 * 8, 16}};
{UI_POS_X(0), 2 * 16, 5 * 8, 16}};
// TODO: delay duration field.
@@ -133,7 +133,7 @@ class PlaylistView : public View {
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
{UI_POS_X_RIGHT(9), 2 * 16},
4,
"Loop",
true};
@@ -145,7 +145,7 @@ class PlaylistView : public View {
Theme::getInstance()->fg_green->background};
Text text_track{
{0 * 8, 3 * 16, screen_width, 16}};
{UI_POS_X(0), 3 * 16, screen_width, 16}};
NewButton button_prev{
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
+6 -6
View File
@@ -85,7 +85,7 @@ class POCSAGTXView : public View {
{{6 * 8, 8 * 8}, "Type:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 8}, "Function:", Theme::getInstance()->fg_light->foreground},
{{5 * 8, 12 * 8}, "Phase:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 14 * 8}, "Message:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 14 * 8}, "Message:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_bitrate{
{11 * 8, 4 * 8},
@@ -122,21 +122,21 @@ class POCSAGTXView : public View {
}};
Text text_message{
{0 * 8, 16 * 8, screen_width, 16},
{UI_POS_X(0), 16 * 8, screen_width, 16},
""};
Text text_message_l2{
{0 * 8, 18 * 8, screen_width, 16},
{UI_POS_X(0), 18 * 8, screen_width, 16},
""};
Button button_message{
{0 * 8, 20 * 8, 14 * 8, 32},
{UI_POS_X(0), 20 * 8, 14 * 8, 32},
"Set message"};
ProgressBar progressbar{
{16, 210, 208, 16}};
{16, 210, UI_POS_WIDTH_REMAINING(4), 16}};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
10000,
9};
+5 -5
View File
@@ -82,7 +82,7 @@ class RDSRadioTextView : public OptionTabView {
{1 * 8, 4 * 16, 28 * 8, 16},
"-"};
Button button_set{
{88, 6 * 16, 64, 32},
{UI_POS_X_CENTER(8), 6 * 16, UI_POS_WIDTH(8), 32},
"Set"};
};
@@ -92,7 +92,7 @@ class RDSDateTimeView : public OptionTabView {
private:
Labels labels{
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
{{UI_POS_X_CENTER(19), 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
};
class RDSAudioView : public OptionTabView {
@@ -101,7 +101,7 @@ class RDSAudioView : public OptionTabView {
private:
Labels labels{
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
{{UI_POS_X_CENTER(19), 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
};
class RDSThread {
@@ -174,7 +174,7 @@ class RDSView : public View {
{"Audio", Theme::getInstance()->fg_orange->foreground, &view_audio}};
Labels labels{
{{0 * 8, 28}, "Program type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 28}, "Program type:", Theme::getInstance()->fg_light->foreground},
//{ { 14 * 8, 16 + 8 }, "CC:", Theme::getInstance()->fg_light->foreground },
{{2 * 8, 28 + 16}, "Program ID:", Theme::getInstance()->fg_light->foreground},
//{ { 13 * 8, 32 + 8 }, "Cov:",Theme::getInstance()->fg_light->foreground },
@@ -319,7 +319,7 @@ class RDSView : public View {
"TP"};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
50000,
9};
+2 -2
View File
@@ -1086,8 +1086,8 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (stepper < 0) stepper++;
if (stepper > 0) stepper--;
} // if( recon || stepper != 0 || index_stepper != 0 )
} // if (frequency_list.size() > 0 )
} /* on_statistics_updates */
} // if (frequency_list.size() > 0 )
} /* on_statistics_updates */
}
handle_retune();
recon_redraw();
+17 -16
View File
@@ -217,23 +217,24 @@ class ReconView : public View {
std::unique_ptr<RecordView> record_view{};
Labels labels{
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(6), UI_POS_Y(0)}, "VOL: ", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 8 * 16}, "START END", Theme::getInstance()->fg_light->foreground},
{{0 * 8, (22 * 8)}, " S: ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), (22 * 8)}, " S: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), (24 * 8) + 4}, "NBLCKS:x W,L: , ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), (26 * 8) + 4}, "MODE: , SQUELCH: ", Theme::getInstance()->fg_light->foreground}};
LNAGainField field_lna{
{4 * 8, 0 * 16}};
{4 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{11 * 8, 0 * 16}};
{11 * 8, UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Text file_name{
// show file used
@@ -297,7 +298,7 @@ class ReconView : public View {
"CONFIG"};
ButtonWithEncoder button_manual_start{
{0 * 8, 9 * 16, 11 * 8, 28},
{UI_POS_X(0), 9 * 16, 11 * 8, 28},
""};
ButtonWithEncoder button_manual_end{
@@ -305,7 +306,7 @@ class ReconView : public View {
""};
OptionsField field_recon_match_mode{
{0 * 8, 11 * 16},
{UI_POS_X(0), 11 * 16},
16, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
{
{"MATCH:CONTINOUS", 0},
@@ -363,15 +364,15 @@ class ReconView : public View {
};
ButtonWithEncoder button_pause{
{0, (15 * 16) - 4, 72, 28},
{0, (15 * 16) - 4, UI_POS_WIDTH(9), 28},
"PAUSE"};
Button button_audio_app{
{84, (15 * 16) - 4, 72, 28},
{UI_POS_X_CENTER(9), (15 * 16) - 4, UI_POS_WIDTH(9), 28},
"AUDIO"};
ButtonWithEncoder button_add{
{168, (15 * 16) - 4, 72, 28},
{UI_POS_X_RIGHT(9), (15 * 16) - 4, UI_POS_WIDTH(9), 28},
"<STORE>"};
Button button_dir{
@@ -383,15 +384,15 @@ class ReconView : public View {
"RST"};
Button button_mic_app{
{84, (35 * 8) - 4, 72, 28},
{UI_POS_X_CENTER(9), (35 * 8) - 4, UI_POS_WIDTH(9), 28},
"MIC TX"};
ButtonWithEncoder button_remove{
{168, (35 * 8) - 4, 72, 28},
{UI_POS_X_RIGHT(9), (35 * 8) - 4, UI_POS_WIDTH(9), 28},
"<REMOVE>"};
ProgressBar progressbar{
{0 * 8, screen_height / 2 - 16, screen_width, 32}};
{UI_POS_X(0), screen_height / 2 - 16, screen_width, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
@@ -225,7 +225,7 @@ class ReconSetupView : public View {
{"More", Theme::getInstance()->fg_green->foreground, &viewMore}};
Button button_save{
{9 * 8, 255, 14 * 8, 40},
{UI_POS_X_CENTER(18), 255, UI_POS_WIDTH(18), 40},
"SAVE"};
};
+37 -11
View File
@@ -26,17 +26,24 @@
#include "binder.hpp"
#include "string_format.hpp"
#include "ui_freqman.hpp"
#include "audio.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
namespace ui {
void SearchLogger::log_data(SearchRecentEntry& data) {
log_file.write_entry(";" + to_string_short_freq(data.frequency) + ";" + std::to_string(data.duration));
}
template <>
void RecentEntriesTable<SearchRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string str_duration = "";
if (entry.duration < 600)
@@ -44,9 +51,10 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
else
str_duration = to_string_dec_uint(entry.duration / 600) + "m" + to_string_dec_uint((entry.duration / 10) % 60) + "s";
str_duration.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, to_string_short_freq(entry.frequency) + " " + entry.time + " " + str_duration);
str_duration.resize(11, ' ');
std::string freq = to_string_short_freq(entry.frequency);
freq.resize(columns.at(0).second, ' ');
painter.draw_string(target_rect.location(), style, freq + " " + entry.time + " " + str_duration);
}
/* SearchView ********************************************/
@@ -54,6 +62,7 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
SearchView::SearchView(
NavigationView& nav)
: nav_(nav) {
spectrum_row.resize(240);
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
if (!gradient.load_file(default_gradient_file)) {
@@ -75,11 +84,12 @@ SearchView::SearchView(
&check_snap,
&options_snap,
&big_display,
&check_log,
&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.set_parent_rect({0, 28 * 8, screen_width, screen_height - 28 * 8});
recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
nav.push<FrequencySaveView>(entry.frequency);
};
@@ -100,6 +110,14 @@ SearchView::SearchView(
on_range_changed();
});
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
if (logging) {
logger.append(logs_dir.string() + "/SEARCH_" + to_string_timestamp(rtc_time::now()) + ".CSV");
logger.write_header();
}
};
bind(field_threshold, settings_.power_threshold);
bind(check_snap, settings_.snap_search);
bind(options_snap, settings_.snap_step);
@@ -108,9 +126,15 @@ SearchView::SearchView(
on_range_changed();
receiver_model.enable();
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
}
SearchView::~SearchView() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
@@ -139,9 +163,8 @@ void SearchView::do_detection() {
// Display spectrum
bin_skip_acc = 0;
pixel_index = 0;
display.draw_pixels(
{{0, 88}, {(Dim)spectrum_row.size(), 1}},
spectrum_row);
uint16_t center_align_start = (screen_width - spectrum_row.size()) / 2;
display.draw_pixels({{center_align_start, 88}, {(Dim)spectrum_row.size(), 1}}, spectrum_row);
mean_power = mean_acc / (SEARCH_BIN_NB_NO_DC * slices_nb);
mean_acc = 0;
@@ -192,7 +215,9 @@ void SearchView::do_detection() {
locked = true;
locked_bin = bin_max;
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
// TODO: open Audio.
} else
text_infos.set("Out of range");
@@ -210,6 +235,7 @@ void SearchView::do_detection() {
auto& entry = ::on_packet(recent, resolved_frequency);
entry.set_duration(duration);
if (logging) logger.log_data(entry);
recent_entries_view.set_dirty();
text_infos.set("Listening");
@@ -225,7 +251,7 @@ void SearchView::do_detection() {
// Refresh red tick
portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Theme::getInstance()->fg_red->background);
if (bin_max > -1) {
last_tick_pos = (Coord)(bin_max / slices_nb);
last_tick_pos = (Coord)(bin_max / slices_nb) + center_align_start;
portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Theme::getInstance()->fg_red->foreground);
}
}
@@ -374,7 +400,7 @@ void SearchView::add_spectrum_pixel(Color color) {
bin_skip_acc -= 0x10000;
if (pixel_index < screen_width)
if (pixel_index < spectrum_row.size())
spectrum_row[pixel_index++] = color;
}
+54 -28
View File
@@ -26,6 +26,8 @@
#include "radio_state.hpp"
#include "gradient.hpp"
#include "ui_receiver.hpp"
#include "log_file.hpp"
#include "file_path.hpp"
namespace ui {
@@ -70,6 +72,21 @@ struct SearchRecentEntry {
using SearchRecentEntries = RecentEntries<SearchRecentEntry>;
class SearchLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_data(SearchRecentEntry& data);
void write_header() {
log_file.write_raw("Time;Freq;Duration;");
}
private:
LogFile log_file{};
};
class SearchView : public View {
public:
SearchView(NavigationView& nav);
@@ -126,7 +143,7 @@ class SearchView : public View {
uint32_t bin_skip_acc = 0;
uint32_t bin_skip_frac = 0;
uint32_t pixel_index = 0;
std::array<Color, 240> spectrum_row{};
std::vector<Color> spectrum_row{};
ChannelSpectrumFIFO* fifo = nullptr;
uint8_t detect_timer = 0;
@@ -148,6 +165,8 @@ class SearchView : public View {
uint16_t locked_bin = 0;
uint8_t search_counter = 0;
bool locked = false;
bool logging = false;
SearchLogger logger{};
void do_detection();
void do_timers();
@@ -155,75 +174,82 @@ class SearchView : public View {
void on_range_changed();
void add_spectrum_pixel(Color color);
const RecentEntriesColumns columns{{{"Frequency", 9},
{"Time", 8},
{"Duration", 11}}};
RecentEntriesColumns columns{{{"Frequency", 0},
{"Time", 8},
{"Duration", 11}}};
SearchRecentEntries recent{};
RecentEntriesView<RecentEntries<SearchRecentEntry>> recent_entries_view{columns, recent};
Labels labels{
{{1 * 8, 0}, "Min: Max: LNA VGA", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 4 * 8}, "Trig: /255 Mean: /255", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 6 * 8}, "Slices: /32 Rate: Hz", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 10 * 8}, "Timer Status", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 25 * 8}, "Accuracy +/-4.9kHz", Theme::getInstance()->fg_light->foreground},
{{26 * 8, 25 * 8}, "MHz", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(1), UI_POS_Y(0)}, "Min: Max: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(7), UI_POS_Y(0)}, "LNA VGA", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(1), UI_POS_Y(2)}, "Trig: /255", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(12), UI_POS_Y(2)}, "Mean: /255", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(1), UI_POS_Y(3)}, "Slices: /32", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(10), UI_POS_Y(3)}, "Rate: Hz", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(6), UI_POS_Y(5)}, "Timer Status", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(1), 25 * 8}, "Accuracy +/-4.9kHz", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(4), 25 * 8}, "MHz", Theme::getInstance()->fg_light->foreground}};
Checkbox check_log{
{UI_POS_X_RIGHT(6), UI_POS_Y(5)},
3,
"LOG",
true};
FrequencyField field_frequency_min{
{1 * 8, 1 * 16}};
{UI_POS_X(1), UI_POS_Y(1)}};
FrequencyField field_frequency_max{
{11 * 8, 1 * 16}};
{UI_POS_X(11), UI_POS_Y(1)}};
LNAGainField field_lna{
{22 * 8, 1 * 16}};
{UI_POS_X_RIGHT(7), UI_POS_Y(1)}};
VGAGainField field_vga{
{26 * 8, 1 * 16}};
{UI_POS_X_RIGHT(3), UI_POS_Y(1)}};
NumberField field_threshold{
{6 * 8, 2 * 16},
{UI_POS_X(6), UI_POS_Y(2)},
3,
{5, 255},
5,
' '};
Text text_mean{
{22 * 8, 2 * 16, 3 * 8, 16},
{UI_POS_X_RIGHT(7), UI_POS_Y(2), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)},
"---"};
Text text_slices{
{8 * 8, 3 * 16, 2 * 8, 16},
{UI_POS_X(8), UI_POS_Y(3), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"--"};
Text text_rate{
{24 * 8, 3 * 16, 3 * 8, 16},
{UI_POS_X_RIGHT(5), UI_POS_Y(3), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)},
"---"};
VuMeter vu_max{
{1 * 8, 11 * 8 - 4, 3 * 8, 48},
{UI_POS_X(1), 11 * 8 - 4, 3 * 8, 48},
18,
false};
ProgressBar progress_timers{
{6 * 8, 12 * 8, 6 * 8, 16}};
{UI_POS_X(6), UI_POS_Y(6), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)}};
Text text_infos{
{13 * 8, 12 * 8, 15 * 8, 16},
{UI_POS_X(13), UI_POS_Y(6), UI_POS_WIDTH(15), UI_POS_HEIGHT(1)},
"Listening"};
Checkbox check_snap{
{6 * 8, 15 * 8},
{UI_POS_X(6), 15 * 8},
7,
"Snap to:",
true};
OptionsField options_snap{
{17 * 8, 15 * 8}, // Position
7, // Length
{ // Options
{UI_POS_X(17), 15 * 8}, // Position
7, // Length
{ // Options
{"25kHz ", 25'000},
{"12.5kHz", 12'500},
{"8.33kHz", 8'333},
{"2.5kHz", 2'500},
{"500Hz", 500}}};
BigFrequency big_display{
{4, 9 * 16, 28 * 8, 52},
0};
BigFrequency big_display{{UI_POS_X_CENTER(28), UI_POS_Y(9), UI_POS_WIDTH(28), 52}, 0};
MessageHandlerRegistration message_handler_spectrum_config{
Message::ID::ChannelSpectrumConfig,
+41 -3
View File
@@ -6,7 +6,7 @@
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* Copyright (C) 2024 HTotoo
* Copyleft (ɔ) 2024 zxkmm under GPL license
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
*
* This file is part of PortaPack.
*
@@ -40,6 +40,7 @@ using namespace lpc43xx;
#include "audio.hpp"
#include "portapack.hpp"
#include "portapack_io.hpp"
using namespace portapack;
#include "file.hpp"
@@ -308,6 +309,39 @@ SetFrequencyCorrectionModel SetRadioView::form_collect() {
};
}
/* SetTXLimitView ************************************/
SetTXLimitView::SetTXLimitView(NavigationView& nav) {
add_children({
&labels,
&tx_gain_max_db,
&tx_disable_switch,
&tx_amp_disable_switch,
&button_save,
&button_cancel,
});
tx_disable_switch.set_value(pmem::config_tx_disabled());
tx_amp_disable_switch.set_value(pmem::config_tx_amp_disabled());
tx_gain_max_db.set_value(pmem::config_tx_gain_max_db());
button_save.on_select = [&nav, this](Button&) {
pmem::set_config_tx_disabled(tx_disable_switch.value());
pmem::set_config_tx_amp_disabled(tx_amp_disable_switch.value());
pmem::set_config_tx_gain_max_db(tx_gain_max_db.value());
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetTXLimitView::focus() {
button_save.focus();
}
/* SetUIView *********************************************/
SetUIView::SetUIView(NavigationView& nav) {
@@ -759,7 +793,7 @@ AppSettingsView::AppSettingsView(
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)});
ensure_directory(settings_dir);
@@ -810,9 +844,12 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
&field_fake_brightness,
&button_save,
&button_cancel,
&checkbox_ips_screen_switch,
&checkbox_brightness_switch});
if (portapack::device_type == portapack::DeviceType::DEV_PORTAPACK) {
add_child(&checkbox_ips_screen_switch);
}
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
@@ -1103,6 +1140,7 @@ void SettingsMenuView::on_populate() {
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<SetPersistentMemoryView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetRadioView>(); }},
{"TX Limit", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetTXLimitView>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SetSDCardView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [this]() { nav_.push<SetUIView>(); }},
{"Display", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetDisplayView>(); }},
+104 -59
View File
@@ -5,7 +5,7 @@
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* Copyleft (ɔ) 2024 zxkmm under GPL license
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
*
* This file is part of PortaPack.
*
@@ -72,8 +72,8 @@ class SetDateTimeView : public View {
{{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Theme::getInstance()->fg_medium->foreground},
{{5 * 8, 6 * 16}, "- - : :", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 12 * 16}, "Start: 0:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 13 * 16}, "End: 1:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground}};
NumberField field_year{
{1 * 8, 6 * 16},
@@ -170,10 +170,10 @@ class SetDateTimeView : public View {
{}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel"};
void form_init(const SetDateTimeModel& model);
@@ -253,10 +253,10 @@ class SetRadioView : public View {
"Disable external TCXO"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
@@ -264,6 +264,51 @@ class SetRadioView : public View {
SetFrequencyCorrectionModel form_collect();
};
class SetTXLimitView : public View {
public:
SetTXLimitView(NavigationView& nav);
void focus() override;
std::string title() const override { return "TX Limit"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Limits RF TX Gain", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "(This may affect", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 3 * 16}, "all applications.)", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 12 * 16}, "TX Max Gain:", Theme::getInstance()->fg_light->foreground},
};
Checkbox tx_disable_switch{
{1 * 8, 6 * 16},
23,
"Disable TX"};
Checkbox tx_amp_disable_switch{
{1 * 8, 8 * 16},
23,
"Disable TX Amp"};
NumberField tx_gain_max_db{
{20 * 8, 12 * 16},
6,
{0, 47},
1,
' ',
};
Button button_save{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
using portapack::persistent_memory::backlight_timeout_t;
class SetUIView : public View {
@@ -372,11 +417,11 @@ class SetUIView : public View {
&bitmap_sd_card_ok};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel"};
};
@@ -391,28 +436,28 @@ class SetSDCardView : public View {
private:
Labels labels{
// 01234567890123456789012345678
{{1 * 8, 120 - 48}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 120 - 32}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X_CENTER(26), 120 - 48}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(26), 120 - 32}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
Checkbox checkbox_sdcard_speed{
{2 * 8, 120},
{UI_POS_X_CENTER(26), 120},
20,
"enable high speed IO"};
Button button_test_sdcard_high_speed{
{2 * 8, 152, 27 * 8, 32},
{UI_POS_X_CENTER(27), 152, UI_POS_WIDTH(27), UI_POS_HEIGHT(2)},
"TEST BUTTON (NO PMEM SAVE)"};
Text text_sdcard_test_status{
{2 * 8, 198, 28 * 8, 16},
{UI_POS_X_CENTER(28), 198, UI_POS_WIDTH(28), UI_POS_HEIGHT(1)},
""};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel"};
};
@@ -455,7 +500,7 @@ class SetConverterSettingsView : public View {
{8 * 8, 10 * 16}};
Button button_return{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Return",
};
};
@@ -498,7 +543,7 @@ class SetFrequencyCorrectionView : public View {
{8 * 8, 10 * 16}};
Button button_return{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, UI_POS_HEIGHT(2)},
"Return",
};
};
@@ -536,11 +581,11 @@ class SetAudioView : public View {
"Beep on RX packets"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -559,11 +604,11 @@ class SetEncoderDialView : public View {
private:
Labels labels{
{{0 * 8, 0 * 16}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 3 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
@@ -606,11 +651,11 @@ class SetEncoderDialView : public View {
"-"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -649,11 +694,11 @@ class SetButtonsView : public View {
{"FAST", true}}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -674,7 +719,7 @@ class SetPersistentMemoryView : public View {
};
Text text_pmem_status{
{1 * 8, 4 * 16 + 8, 28 * 8, 16},
{UI_POS_X_CENTER(28), 4 * 16 + 8, 28 * 8, 16},
""};
Checkbox check_use_sdcard_for_pmem{
@@ -683,19 +728,19 @@ class SetPersistentMemoryView : public View {
"Use SD card for P.Mem"};
Button button_save_mem_to_file{
{1 * 8, 8 * 16, 28 * 8, 2 * 16},
{UI_POS_X_CENTER(28), 8 * 16, 28 * 8, 2 * 16},
"Save P.Mem to SD card"};
Button button_load_mem_from_file{
{1 * 8, 10 * 16 + 2, 28 * 8, 2 * 16},
{UI_POS_X_CENTER(28), 10 * 16 + 2, 28 * 8, 2 * 16},
"Load P.Mem from SD Card"};
Button button_load_mem_defaults{
{1 * 8, 12 * 16 + 4, 28 * 8, 2 * 16},
{UI_POS_X_CENTER(28), 12 * 16 + 4, 28 * 8, 2 * 16},
"Reset P.Mem to defaults"};
Button button_return{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Return",
};
};
@@ -713,7 +758,7 @@ class AppSettingsView : public View {
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, screen_width, 26 * 8},
{0, 2 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)},
true};
};
@@ -738,11 +783,11 @@ class SetConfigModeView : public View {
"Config Mode enable"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -784,11 +829,11 @@ class SetDisplayView : public View {
"IPS Screen"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
@@ -841,19 +886,19 @@ class SetTouchscreenThresholdView : public View {
};
Button button_autodetect{
{2 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 13 * 16, 12 * 8, 32},
"Auto Detect"};
Button button_reset{
{16 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 13 * 16, 12 * 8, 32},
"Reset",
};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 16 * 16, 12 * 8, 32},
"Cancel",
};
@@ -876,14 +921,14 @@ class SetMenuColorView : public View {
void paint_sample();
Labels labels{
{{3 * 8, 1 * 16}, "Menu Button Color Scheme", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(25), 1 * 16}, "Menu Button Color Scheme", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 8 * 16}, "Red Level:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 9 * 16}, "Green Level:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "Blue Level:", Theme::getInstance()->fg_light->foreground},
};
NewButton button_sample{
{8 * 8, 4 * 16, 14 * 8, 3 * 16},
{UI_POS_X_CENTER(14), 4 * 16, 14 * 8, 3 * 16},
"New Color",
&bitmap_icon_brightness,
};
@@ -913,16 +958,16 @@ class SetMenuColorView : public View {
};
Button button_reset{
{2 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(7), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Reset",
};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
@@ -941,12 +986,8 @@ class SetThemeView : public View {
{{1 * 8, 1 * 16}, "Select a theme.", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "Restart PP to fully apply!", Theme::getInstance()->fg_light->foreground}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
OptionsField options{
{0 * 8, 4 * 16},
{UI_POS_X(0), 4 * 16},
(size_t)(screen_width / 8),
{
{"Default - Grey", 0},
@@ -963,8 +1004,12 @@ class SetThemeView : public View {
23,
"Set Menu color too"};
Button button_save{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
@@ -988,10 +1033,6 @@ class SetBatteryView : public View {
Labels labels2{{{1 * 8, 11 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Checkbox checkbox_overridebatt{
{2 * 8, 4 * 16},
23,
@@ -1003,12 +1044,16 @@ class SetBatteryView : public View {
"Charge hint"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
Button button_save{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_reset{
{2 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y(13), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Reset",
};
};
+35 -12
View File
@@ -30,11 +30,10 @@
#include "portapack.hpp"
#include <cstring>
#include <stdio.h>
#include "rtc_time.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
#include "string_format.hpp"
#include "complex.hpp"
void SondeLogger::on_packet(const sonde::Packet& packet) {
@@ -54,6 +53,7 @@ SondeView::SondeView(NavigationView& nav)
&field_lna,
&field_vga,
&rssi,
&channel,
&field_volume,
&check_log,
&check_crc,
@@ -64,6 +64,8 @@ SondeView::SondeView(NavigationView& nav)
&text_frame,
&text_temp,
&text_humid,
&text_press,
&text_vspeed,
&geopos,
&button_see_qr,
&button_see_map});
@@ -106,7 +108,7 @@ SondeView::SondeView(NavigationView& nav)
logger = std::make_unique<SondeLogger>();
if (logger)
logger->append(logs_dir / u"SONDE.TXT");
logger->append(logs_dir / u"SONDE_" + to_string_timestamp(rtc_time::now()) + u".TXT");
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
@@ -154,7 +156,7 @@ void SondeView::on_packet(const sonde::Packet& packet) {
sonde_id = packet.serial_number(); // used also as tag on the geomap
text_serial.set(sonde_id);
text_timestamp.set(to_string_timestamp(packet.received_at()));
text_timestamp.set(to_string_datetime(packet.received_at(), TimeFormat::YMDHMS));
text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 2) + "V");
@@ -162,21 +164,42 @@ void SondeView::on_packet(const sonde::Packet& packet) {
temp_humid_info = packet.get_temp_humid();
if (temp_humid_info.humid != 0) {
double decimals = abs(get_decimals(temp_humid_info.humid, 10, true));
text_humid.set(to_string_dec_int((int)temp_humid_info.humid) + "." + to_string_dec_uint(decimals, 1) + "%");
text_humid.set(to_string_decimal(temp_humid_info.humid, 1) + "%");
}
if (temp_humid_info.temp != 0) {
double decimals = abs(get_decimals(temp_humid_info.temp, 10, true));
text_temp.set(to_string_dec_int((int)temp_humid_info.temp) + "." + to_string_dec_uint(decimals, 1) + STR_DEGREES_C);
text_temp.set(to_string_decimal(temp_humid_info.temp, 1) + STR_DEGREES_C);
}
if (packet.get_pressure() != 0) {
text_press.set(to_string_decimal(packet.get_pressure(), 1) + " hPa");
}
gps_info = packet.get_GPS_data();
geopos.set_altitude(gps_info.alt);
geopos.set_lat(gps_info.lat);
geopos.set_lon(gps_info.lon);
if (last_timestamp_update_ != 0 && last_altitude_ != 0) {
// calculate speeds
float vspeed = 0;
time_t currpackettime = rtc_time::rtcToUnixUTC(packet.received_at());
int32_t time_diff = (currpackettime - last_timestamp_update_);
if (time_diff >= 10) { // update only every 10 seconds
vspeed = (static_cast<int>(gps_info.alt) - static_cast<int>(last_altitude_)) / (float)time_diff;
last_timestamp_update_ = currpackettime;
last_altitude_ = gps_info.alt;
text_vspeed.set(to_string_decimal(vspeed, 1) + " m/s");
}
} else { // save first valid packet time + altitude
last_timestamp_update_ = rtc_time::rtcToUnixUTC(packet.received_at());
last_altitude_ = geopos.altitude();
}
if (gps_info.is_valid()) { // only update when valid, to prevent flashing
geopos.set_altitude(gps_info.alt);
geopos.set_lat(gps_info.lat);
geopos.set_lon(gps_info.lon);
if (geomap_view_) {
geomap_view_->update_position(gps_info.lat, gps_info.lon, 400, gps_info.alt, 0);
}
}
if (logger && logging) {
logger->on_packet(packet);
}
+41 -26
View File
@@ -30,6 +30,7 @@
#include "ui_rssi.hpp"
#include "ui_qrcode.hpp"
#include "ui_geomap.hpp"
#include "string_format.hpp"
#include "event_m0.hpp"
@@ -94,86 +95,100 @@ class SondeView : public View {
// AudioOutput audio_output { };
Labels labels{
{{4 * 8, 2 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 3 * 16}, "ID:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "DateTime:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(4), UI_POS_Y(2)}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(6), UI_POS_Y(3)}, "ID:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(4)}, "DateTime:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 5 * 16}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 6 * 16}, "Frame:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 7 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(3), UI_POS_Y(5)}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(3), UI_POS_Y(6)}, "Frame:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(4), UI_POS_Y(7)}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(8)}, "Humidity:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(9)}, "Pressure:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(10)}, "VSpeed:", Theme::getInstance()->fg_light->foreground}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{UI_POS_X(21), UI_POS_Y(0) + 5, UI_POS_WIDTH_REMAINING(24), 4},
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Checkbox check_log{
{22 * 8, 8 * 16},
{UI_POS_X_RIGHT(8), UI_POS_Y(8)},
3,
"Log"};
Checkbox check_crc{
{22 * 8, 10 * 16},
{UI_POS_X_RIGHT(8), UI_POS_Y(10)},
3,
"CRC"};
Text text_signature{
{9 * 8, 2 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(2), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
"..."};
Text text_serial{
{9 * 8, 3 * 16, 11 * 8, 16},
{UI_POS_X(9), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
"..."};
Text text_timestamp{
{9 * 8, 4 * 16, 11 * 8, 16},
{UI_POS_X(9), UI_POS_Y(4), UI_POS_WIDTH_REMAINING(9), UI_POS_HEIGHT(1)},
"..."};
Text text_voltage{
{9 * 8, 5 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(5), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_frame{
{9 * 8, 6 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(6), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_temp{
{9 * 8, 7 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(7), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_humid{
{9 * 8, 8 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(8), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_press{
{UI_POS_X(9), UI_POS_Y(9), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_vspeed{
{UI_POS_X(9), UI_POS_Y(10), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
GeoPos geopos{
{0, 12 * 16},
{UI_POS_X(0), UI_POS_Y(12)},
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN};
Button button_see_qr{
{2 * 8, 15 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See QR"};
Button button_see_map{
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See on map"};
GeoMapView* geomap_view_{nullptr};
time_t last_timestamp_update_{0};
uint32_t last_altitude_{0};
MessageHandlerRegistration message_handler_packet{
Message::ID::SondePacket,
@@ -27,6 +27,9 @@
#include "ui_font_fixed_5x8.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "portapack.hpp"
#include "file.hpp"
namespace ui {
@@ -93,8 +96,90 @@ bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) {
return dev->i2c_read(cmd, cmd_len, data, data_len);
}
// v3
// Version 3
FRESULT ext_f_open(FIL* fp, const TCHAR* path, BYTE mode) {
return f_open(fp, path, mode);
}
FRESULT ext_f_close(FIL* fp) {
return f_close(fp);
}
FRESULT ext_f_read(FIL* fp, void* buff, UINT btr, UINT* br) {
return f_read(fp, buff, btr, br);
}
FRESULT ext_f_write(FIL* fp, const void* buff, UINT btw, UINT* bw) {
return f_write(fp, buff, btw, bw);
}
FRESULT ext_f_lseek(FIL* fp, FSIZE_t ofs) {
return f_lseek(fp, ofs);
}
FRESULT ext_f_truncate(FIL* fp) {
return f_truncate(fp);
}
FRESULT ext_f_sync(FIL* fp) {
return f_sync(fp);
}
FRESULT ext_f_opendir(DIR* dp, const TCHAR* path) {
return f_opendir(dp, path);
}
FRESULT ext_f_closedir(DIR* dp) {
return f_closedir(dp);
}
FRESULT ext_f_readdir(DIR* dp, FILINFO* fno) {
return f_readdir(dp, fno);
}
FRESULT ext_f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern) {
return f_findfirst(dp, fno, path, pattern);
}
FRESULT ext_f_findnext(DIR* dp, FILINFO* fno) {
return f_findnext(dp, fno);
}
FRESULT ext_f_mkdir(const TCHAR* path) {
return f_mkdir(path);
}
FRESULT ext_f_unlink(const TCHAR* path) {
return f_unlink(path);
}
FRESULT ext_f_rename(const TCHAR* path_old, const TCHAR* path_new) {
return f_rename(path_old, path_new);
}
FRESULT ext_f_stat(const TCHAR* path, FILINFO* fno) {
return f_stat(path, fno);
}
FRESULT ext_f_utime(const TCHAR* path, const FILINFO* fno) {
return f_utime(path, fno);
}
FRESULT ext_f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs) {
return f_getfree(path, nclst, fatfs);
}
FRESULT ext_f_mount(FATFS* fs, const TCHAR* path, BYTE opt) {
return f_mount(fs, path, opt);
}
int ext_f_putc(TCHAR c, FIL* fp) {
return f_putc(c, fp);
}
int ext_f_puts(const TCHAR* str, FIL* cp) {
return f_puts(str, cp);
}
int ext_f_printf(FIL* fp, const TCHAR* str, ...) {
return f_printf(fp, str);
}
TCHAR* ext_f_gets(TCHAR* buff, int len, FIL* fp) {
return f_gets(buff, len, fp);
}
void ext_draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count) {
portapack::display.draw_pixels(r, colors, count);
}
void ext_draw_pixel(const ui::Point p, const ui::Color color) {
portapack::display.draw_pixel(p, color);
}
StandaloneView* standaloneView = nullptr;
void exit_app() {
if (standaloneView) standaloneView->exit();
}
void set_dirty() {
if (standaloneView != nullptr)
standaloneView->set_dirty();
@@ -121,6 +206,36 @@ standalone_application_api_t api = {
/* .i2c_read = */ &i2c_read,
/* .panic = */ &chDbgPanic,
/* .set_dirty = */ &set_dirty,
// Version 3
.f_open = &ext_f_open,
.f_close = &ext_f_close,
.f_read = &ext_f_read,
.f_write = &ext_f_write,
.f_lseek = &ext_f_lseek,
.f_truncate = &ext_f_truncate,
.f_sync = &ext_f_sync,
.f_opendir = &ext_f_opendir,
.f_closedir = &ext_f_closedir,
.f_readdir = &ext_f_readdir,
.f_findfirst = &ext_f_findfirst,
.f_findnext = &ext_f_findnext,
.f_mkdir = &ext_f_mkdir,
.f_unlink = &ext_f_unlink,
.f_rename = &ext_f_rename,
.f_stat = &ext_f_stat,
.f_utime = &ext_f_utime,
.f_getfree = &ext_f_getfree,
.f_mount = &ext_f_mount,
.f_putc = &ext_f_putc,
.f_puts = &ext_f_puts,
.f_printf = &ext_f_printf,
.f_gets = &ext_f_gets,
.draw_pixels = &ext_draw_pixels,
.draw_pixel = &ext_draw_pixel,
.exit_app = &exit_app,
// version 4
.screen_height = &screen_height,
.screen_width = &screen_width,
};
StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
@@ -158,22 +273,21 @@ bool StandaloneView::on_key(const KeyEvent key) {
if (get_application_information()->header_version > 1) {
return get_application_information()->OnKeyEvent((uint8_t)key);
}
return false;
return true;
}
bool StandaloneView::on_encoder(const EncoderEvent event) {
if (get_application_information()->header_version > 1) {
return get_application_information()->OnEncoder((int32_t)event);
}
return false;
return true;
}
bool StandaloneView::on_touch(const TouchEvent event) {
if (get_application_information()->header_version > 1) {
get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
return get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
}
return true;
return false;
}
bool StandaloneView::on_keyboard(const KeyboardEvent event) {
@@ -202,4 +316,8 @@ void StandaloneView::on_before_detach() {
context().focus_manager().clearMirror();
}
void StandaloneView::exit() {
nav_.pop();
}
} // namespace ui
@@ -51,6 +51,8 @@ class StandaloneView : public View {
void frame_sync();
void exit();
private:
bool initialized = false;
NavigationView& nav_;
+13 -8
View File
@@ -242,6 +242,8 @@ const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) {
return "GangQi";
case FPS_MARANTEC24:
return "Marantec24";
case FPS_HOLTEKHT6P20B:
return "Holtek HT6P20B";
case FPS_Invalid:
default:
return "Unknown";
@@ -262,17 +264,14 @@ void RecentEntriesTable<ui::SubGhzDRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string line{};
line.reserve(30);
line = SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)entry.sensorType);
line = line + " " + to_string_hex(entry.data << 32);
if (line.length() < 19) {
line += SubGhzDView::pad_string_with_spaces(19 - line.length());
} else {
line = truncate(line, 19);
}
line.resize(columns.at(0).second, ' ');
std::string ageStr = to_string_dec_uint(entry.age);
std::string bitsStr = to_string_dec_uint(entry.bits);
line += SubGhzDView::pad_string_with_spaces(5 - bitsStr.length()) + bitsStr;
@@ -342,7 +341,7 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
if (entry_.sensorType == FPS_CAMEATOMO) {
entry_.data ^= 0xFFFFFFFFFFFFFFFF;
entry_.data <<= 4;
uint8_t pack[8] = {};
uint8_t pack[8];
pack[0] = (entry_.data >> 56);
pack[1] = ((entry_.data >> 48) & 0xFF);
pack[2] = ((entry_.data >> 40) & 0xFF);
@@ -759,5 +758,11 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
btn = entry_.data & 0xf;
return;
}
if (entry_.sensorType == FPS_HOLTEKHT6P20B) {
serial = entry_.data >> 8;
btn = (entry_.data >> 4) & 0xF;
return;
}
}
} // namespace ui
} // namespace ui
+12 -12
View File
@@ -122,18 +122,18 @@ class SubGhzDView : public View {
SubGhzDRecentEntries recent{};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
SignalToken signal_token_tick_second{};
@@ -152,8 +152,8 @@ class SubGhzDView : public View {
std::unique_ptr<SubGhzDLogger> logger{};
const RecentEntriesColumns columns{{
{"Type", 19},
RecentEntriesColumns columns{{
{"Type", 0},
{"Bits", 4},
{"Age", 3},
}};
@@ -191,16 +191,16 @@ class SubGhzDRecentEntryDetailView : public View {
uint32_t cnt = SD_NO_CNT;
uint32_t seed = 0;
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"};
Text text_type{{UI_POS_X(0), 1 * 16, 15 * 8, 16}, "?"};
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Console console{
{0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
Labels labels{
{{0 * 8, 0 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Serial: ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Data:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 2 * 16}, "Serial: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Data:", Theme::getInstance()->fg_light->foreground},
};
Button button_done{
+7 -7
View File
@@ -76,29 +76,29 @@ class TestView : public View {
bool logging{false};
Labels labels{
{{0 * 8, 1 * 16}, "Data:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 1 * 16}, "Data:", Theme::getInstance()->fg_light->foreground}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
{UI_POS_X(0), 0 * 8},
nav_};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
Text text_debug_a{
{0 * 8, 4 * 16, screen_width, 16},
{UI_POS_X(0), 4 * 16, screen_width, 16},
"..."};
Text text_debug_b{
{0 * 8, 5 * 16, screen_width, 16},
{UI_POS_X(0), 5 * 16, screen_width, 16},
"..."};
Button button_cal{
+2 -2
View File
@@ -427,7 +427,7 @@ void TextViewer::set_font_zoom(bool zoom) {
/* TextEditorMenu ***************************************************/
TextEditorMenu::TextEditorMenu()
: View{{7 * 4, 9 * 4, 25 * 8, 25 * 8}} {
: View{{UI_POS_X_CENTER(25), 9 * 4, 25 * 8, 25 * 8}} {
add_children(
{
&rect_frame,
@@ -612,7 +612,7 @@ void TextEditorView::open_file(const fs::path& path) {
viewer.clear_file();
delete_temp_file(path_);
path_ = {};
path_ = "";
file_dirty_ = false;
has_temp_file_ = false;
auto result = FileWrapper::open(
+5 -5
View File
@@ -164,7 +164,7 @@ class TextEditorMenu : public View {
void hide_children(bool hidden);
Rectangle rect_frame{
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
{UI_POS_X(0), 0 * 8, 23 * 8, 23 * 8},
Theme::getInstance()->fg_dark->foreground};
NewButton button_home{
@@ -267,23 +267,23 @@ class TextEditorView : public View {
TextViewer viewer{
/* 272 = screen_height - 16 (top bar) - 32 (bottom controls) */
{0, 0, screen_width, 272}};
{0, 0, screen_width, UI_POS_HEIGHT_REMAINING(4)}};
TextEditorMenu menu{};
NewButton button_menu{
{26 * 8, 34 * 8, 4 * 8, 4 * 8},
{UI_POS_X_RIGHT(4), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_controls,
Theme::getInstance()->bg_dark->background,
/*vcenter*/ true};
Text text_position{
{0 * 8, 34 * 8, 26 * 8, 2 * 8},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT(1)},
""};
Text text_size{
{0 * 8, 36 * 8, 26 * 8, 2 * 8},
{UI_POS_X(0), UI_POS_Y_BOTTOM(2), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT(1)},
""};
};
@@ -81,13 +81,13 @@ class TouchCalibrationView : public View {
Color::black()};
Image image_calibrate_1{
{240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32},
{screen_width - 32 - 16, (screen_height - 16) / 2 - 16, 32, 32},
&bitmap_target_calibrate,
Color::white(),
Color::black()};
Image image_calibrate_2{
{240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32},
{screen_width / 2 - 16, (screen_height - 16) - 32 - 16, 32, 32},
&bitmap_target_calibrate,
Color::white(),
Color::black()};
@@ -99,13 +99,13 @@ class TouchCalibrationView : public View {
Color::black()};
Image image_verify_1{
{240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32},
{screen_width - 32 - 16, (screen_height - 16) / 2 - 16, 32, 32},
&bitmap_target_verify,
Color::white(),
Color::black()};
Image image_verify_2{
{240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32},
{screen_width / 2 - 16, (screen_height - 16) - 32 - 16, 32, 32},
&bitmap_target_verify,
Color::white(),
Color::black()};
@@ -131,11 +131,11 @@ class TouchCalibrationView : public View {
"Calibration failed. Retry?"};
Button button_cancel{
{40, 200, 64, 24},
{40, 200, UI_POS_WIDTH(8), 24},
"Cancel"};
Button button_ok{
{136, 200, 64, 24},
{136, 200, UI_POS_WIDTH(8), 24},
"OK"};
void on_frame_sync();
+4 -4
View File
@@ -162,16 +162,16 @@ class TouchTunesView : public View {
{{18 * 8 + 4, 18 * 8}, "V"},
{{22 * 8, 18 * 8}, "F4"},
{{0 * 8, 5 * 8}, "1"},
{{UI_POS_X(0), 5 * 8}, "1"},
{{4 * 8, 5 * 8}, "2"},
{{8 * 8, 5 * 8}, "3"},
{{0 * 8, 10 * 8}, "4"},
{{UI_POS_X(0), 10 * 8}, "4"},
{{4 * 8, 10 * 8}, "5"},
{{8 * 8, 10 * 8}, "6"},
{{0 * 8, 15 * 8}, "7"},
{{UI_POS_X(0), 15 * 8}, "7"},
{{4 * 8, 15 * 8}, "8"},
{{8 * 8, 15 * 8}, "9"},
{{0 * 8, 20 * 8}, "*"},
{{UI_POS_X(0), 20 * 8}, "*"},
{{4 * 8, 20 * 8}, "0"},
{{8 * 8, 20 * 8}, "#"},
+39 -12
View File
@@ -197,6 +197,10 @@ WeatherView::~WeatherView() {
baseband::shutdown();
}
uint16_t WeatherView::bcd_decode_short(uint32_t data) {
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
}
const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
switch (type) {
case FPW_NexusTH:
@@ -225,6 +229,8 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
return "LaCrosse TX141THBv2";
case FPW_OREGON2:
return "Oregon2";
case FPW_OREGON2B:
return "Oregon2B";
case FPW_OREGON3:
return "Oregon3";
case FPW_OREGONv1:
@@ -270,16 +276,13 @@ void RecentEntriesTable<ui::WeatherRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string line{};
line.reserve(30);
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType);
if (line.length() < 10) {
line += WeatherView::pad_string_with_spaces(10 - line.length());
} else {
line = truncate(line, 10);
}
line.resize(columns.at(0).second, ' ');
std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 1));
std::string humStr = (entry.humidity != WS_NO_HUMIDITY) ? to_string_dec_uint(entry.humidity) + "%" : "-";
@@ -302,7 +305,9 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
WeatherRecentEntry ret = {};
ret.sensorType = data->sensorType;
int16_t i16 = 0;
int32_t i32 = 0;
uint16_t u16 = 0;
uint64_t u64 = 0;
uint8_t u8 = 0;
uint8_t channel_3021[] = {3, 0, 2, 1};
@@ -445,12 +450,34 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
ret.humidity = (data->decode_data >> 8) & 0xFF;
break;
case FPW_OREGON2:
i16 = ((data->decode_data >> 4) & 0xF) * 10 + (((data->decode_data >> 4) >> 4) & 0xF);
i16 *= 10;
i16 += (data->decode_data >> 12) & 0xF;
if (data->decode_data & 0xF) i16 = -i16;
ret.temp = (float)i16 / 10.0;
// todo fix missing parts.
i32 = bcd_decode_short(data->decode_data >> 4);
i32 *= 10;
i32 += (data->decode_data >> 12) & 0xf;
if (data->decode_data & 0xF) i32 = -i32;
ret.temp = (float)i32 / 10.0;
ret.battery_low = ((data->decode_data >> 32) & 0x4) ? 1 : 0;
u8 = (data->decode_data >> 44) & 0xF;
ret.channel = 1;
while (u8 > 1) {
ret.channel++;
u8 >>= 1;
}
break;
case FPW_OREGON2B:
ret.humidity = bcd_decode_short(data->decode_data);
u64 = data->decode_data >> 8;
i32 = bcd_decode_short(u64 >> 4);
i32 *= 10;
i32 += (u64 >> 12) & 0xf;
if (u64 & 0xF) i32 = -i32;
ret.temp = (float)i32 / 10.0;
ret.battery_low = ((data->decode_data >> 32) & 0x4) ? 1 : 0;
u8 = (data->decode_data >> 44) & 0xF;
ret.channel = 1;
while (u8 > 1) {
ret.channel++;
u8 >>= 1;
}
break;
case FPW_OREGON3:
// todo check
+19 -18
View File
@@ -114,6 +114,7 @@ class WeatherView : public View {
private:
void on_tick_second();
uint16_t bcd_decode_short(uint32_t data);
void on_data(const WeatherDataMessage* data);
WeatherRecentEntry process_data(const WeatherDataMessage* data);
NavigationView& nav_;
@@ -134,28 +135,28 @@ class WeatherView : public View {
WeatherRecentEntries recent{};
OptionsField options_temperature{
{10 * 8, 0 * 16},
{10 * 8, UI_POS_Y(0)},
2,
{{STR_DEGREES_C, 0},
{STR_DEGREES_F, 1}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
SignalToken signal_token_tick_second{};
@@ -174,8 +175,8 @@ class WeatherView : public View {
std::unique_ptr<WeatherLogger> logger{};
const RecentEntriesColumns columns{{
{"Type", 10},
RecentEntriesColumns columns{{
{"Type", 0},
{"Temp", 5},
{"Hum", 4},
{"Ch", 3},
@@ -218,14 +219,14 @@ class WeatherRecentEntryDetailView : public View {
Text text_age{{10 * 8, 7 * 16, 10 * 8, 16}, "?"};
Labels labels{
{{0 * 8, 0 * 16}, "Weather Station", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 1 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Id: ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Channel:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Battery:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Age:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Weather Station", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 1 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 2 * 16}, "Id: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 4 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 5 * 16}, "Channel:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 6 * 16}, "Battery:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "Age:", Theme::getInstance()->fg_light->foreground},
};
Button button_done{
+35 -2
View File
@@ -147,6 +147,19 @@ void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration) {
send_message(&message);
}
void set_sstvrx_data(const uint8_t code) {
const SSTVRXConfigureMessage message{
code};
send_message(&message);
}
void set_sstvrx_phase_slant(const int16_t phase, const int16_t slant) {
const SSTVRXPhaseSlantMessage message{
phase,
slant};
send_message(&message);
}
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) {
const AFSKRxConfigureMessage message{
baudrate,
@@ -288,8 +301,8 @@ void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit,
send_message(&message);
}
void set_pocsag() {
const POCSAGConfigureMessage message{};
void set_pocsag(int8_t baud_config) {
const POCSAGConfigureMessage message{baud_config};
send_message(&message);
}
@@ -328,6 +341,11 @@ void set_noaaapt_config() {
send_message(&message);
}
void set_flex_config() {
const FlexConfigureMessage message{};
send_message(&message);
}
void set_siggen_tone(const uint32_t tone) {
const SigGenToneMessage message{
TONES_F2D(tone, TONES_SAMPLERATE)};
@@ -350,6 +368,21 @@ void set_subghzd_config(uint8_t modulation = 0, uint32_t sampling_rate = 0) {
send_message(&message);
}
void set_moreserx_config(uint8_t mode) {
const MorseRXConfigureMessage message{mode};
send_message(&message);
}
void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta) {
const MorseTXConfigureMessage message{mode, tone, fm_delta};
send_message(&message);
}
void set_morsetx_key(bool key_down) {
const MorseTXkeyMessage message{key_down};
send_message(&message);
}
static bool baseband_image_running = false;
void run_image(const spi_flash::image_tag_t image_tag) {
+8 -2
View File
@@ -74,13 +74,15 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone();
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
void set_sstvrx_data(const uint8_t code);
void set_sstvrx_phase_slant(const int16_t phase, const int16_t slant);
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
void set_fifo_data(const int8_t* data);
void set_pitch_rssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
void kill_afsk();
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
void set_fsk(const uint32_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
void set_aprs(const uint32_t baudrate);
void set_btlerx(uint8_t channel_number);
@@ -91,7 +93,7 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint8_t repeat, const uint32_t pause_symbols, const uint8_t de_bruijn_length = 0);
void kill_ook();
void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint32_t shift, const uint32_t progress_notice);
void set_pocsag();
void set_pocsag(int8_t baud_config = -1);
void set_adsb();
void set_jammer(const bool run, const jammer::JammerType type, const uint32_t speed);
void set_rds_data(const uint16_t message_length);
@@ -100,8 +102,12 @@ void set_siggen_tone(const uint32_t tone);
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
void set_moreserx_config(uint8_t mode);
void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta);
void set_morsetx_key(bool key_down);
void set_wefax_config(uint8_t lpm, uint8_t ioc);
void set_noaaapt_config();
void set_flex_config();
void request_roger_beep();
void request_rssi_beep();
+41 -3
View File
@@ -415,6 +415,44 @@ static constexpr Bitmap bitmap_icon_adsb{
{16, 16},
bitmap_icon_adsb_data};
static constexpr uint8_t bitmap_icon_adulttoys_data[] = {
0x00,
0x00,
0x30,
0x0C,
0x78,
0x1E,
0x7C,
0x3E,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFC,
0x3F,
0xFC,
0x3F,
0xF8,
0x1F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x80,
0x01,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_adulttoys{
{16, 16},
bitmap_icon_adulttoys_data};
static constexpr uint8_t bitmap_icon_ais_data[] = {
0x00,
0x01,
@@ -4259,7 +4297,7 @@ static constexpr Bitmap bitmap_icon_tpms{
{16, 16},
bitmap_icon_tpms_data};
static constexpr uint8_t bitmap_icon_tranceivers_data[] = {
static constexpr uint8_t bitmap_icon_transceivers_data[] = {
0x80,
0x01,
0xC0,
@@ -4293,9 +4331,9 @@ static constexpr uint8_t bitmap_icon_tranceivers_data[] = {
0xFF,
0xFF,
};
static constexpr Bitmap bitmap_icon_tranceivers{
static constexpr Bitmap bitmap_icon_transceivers{
{16, 16},
bitmap_icon_tranceivers_data};
bitmap_icon_transceivers_data};
static constexpr uint8_t bitmap_icon_transmit_data[] = {
0x80,
+6 -12
View File
@@ -244,8 +244,7 @@ static void portapack_tcxo_enable() {
/* Delay >10ms at 96MHz clock speed for reference oscillator to start. */
/* Delay an additional 1ms (arbitrary) for the clock generator to detect a signal. */
volatile uint32_t delay = 240000 + 24000;
while (delay--)
;
while (delay--);
}
static void portapack_tcxo_disable() {
@@ -329,8 +328,7 @@ void ClockManager::init_clock_generator() {
: (ref_pll == ClockControl::MultiSynthSource::PLLB)
? 0x40
: 0x20;
while ((clock_generator.device_status() & device_status_mask) != 0)
;
while ((clock_generator.device_status() & device_status_mask) != 0);
clock_generator.set_clock_control(
clock_generator_output_mcu_clkin,
@@ -377,8 +375,7 @@ ClockManager::Reference ClockManager::choose_reference() {
if (hackrf_r9) {
gpio_r9_clkin_en.write(1);
volatile uint32_t delay = 240000 + 24000;
while (delay--)
;
while (delay--);
}
const auto detected_reference = detect_reference_source();
@@ -513,8 +510,7 @@ void ClockManager::start_frequency_monitor_measurement(const cgu::CLK_SEL clk_se
void ClockManager::wait_For_frequency_monitor_measurement_done() {
// FREQ_MON mechanism fails to finish if there's no clock present on selected input?!
while (LPC_CGU->FREQ_MON.MEAS == 1)
;
while (LPC_CGU->FREQ_MON.MEAS == 1);
}
uint32_t ClockManager::get_frequency_monitor_measurement_in_hertz() {
@@ -559,8 +555,7 @@ void ClockManager::start_audio_pll() {
});
cgu::pll0audio::power_up();
while (!cgu::pll0audio::is_locked())
;
while (!cgu::pll0audio::is_locked());
cgu::pll0audio::clock_enable();
set_base_audio_clock_divider(1);
@@ -577,8 +572,7 @@ void ClockManager::set_base_audio_clock_divider(const size_t divisor) {
void ClockManager::stop_audio_pll() {
cgu::pll0audio::clock_disable();
cgu::pll0audio::power_down();
while (cgu::pll0audio::is_locked())
;
while (cgu::pll0audio::is_locked());
}
void ClockManager::enable_clock_output(bool enable) {
+3 -6
View File
@@ -139,13 +139,11 @@ 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 (swizzled_switches() & (1 << (int)Switch::Dfu));
while (true) {
volatile size_t n = 1000000U;
while (n--)
;
while (n--);
led.toggle();
// Stack dump will cover entire screen, so wait for DFU button press to attempt it
@@ -225,8 +223,7 @@ void draw_stack_dump() {
// Out of room on the screen or end of stack - allow Up/Down paging.
// First wait for button release from previous press.
// NOTE: can't call swizzle_switches() with interrupted enabled!
while (swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu)))
;
while (swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu)));
painter.draw_string({border, portapack::display.height() - border - 8}, *Theme::getInstance()->bg_darkest_small, "Use UP/DOWN key");
+1 -2
View File
@@ -41,8 +41,7 @@ extern uint32_t __process_stack_end__;
inline uint32_t get_free_stack_space() {
uint32_t* p;
for (p = &__process_stack_base__; *p == CRT0_STACKS_FILL_PATTERN && p < &__process_stack_end__; p++)
;
for (p = &__process_stack_base__; *p == CRT0_STACKS_FILL_PATTERN && p < &__process_stack_end__; p++);
auto stack_space_left = p - &__process_stack_base__;
return stack_space_left;
+13 -16
View File
@@ -124,12 +124,12 @@ void EventDispatcher::request_stop() {
void EventDispatcher::set_display_sleep(const bool sleep) {
// TODO: Distribute display sleep message more broadly, shut down data generation
// on baseband side, since all that data is being discarded during sleep.
// on baseband side, since all that data is being discarded during sleep. -- DON'T TODO it, sincethe stealth mode want to send with screen off!
if (sleep) {
portapack::backlight()->off();
portapack::display.sleep();
portapack::display.sleep(false); // when called the hw_sleep = true, the irq wont fire, so the EVT_MASK_LCD_FRAME_SYNC won't set.
} else {
portapack::display.wake();
portapack::display.wake(true); // not important, command not affect if already hw waken up
// Don't turn on backlight here.
// Let frame sync handler turn on backlight after repaint.
}
@@ -180,12 +180,10 @@ void EventDispatcher::dispatch(const eventmask_t events) {
/*if( events & EVT_MASK_LCD_FRAME_SYNC ) {
blink_timer();
}*/
if (events & EVT_MASK_LCD_FRAME_SYNC) {
handle_lcd_frame_sync(!EventDispatcher::display_sleep);
}
if (!EventDispatcher::display_sleep) {
if (events & EVT_MASK_LCD_FRAME_SYNC) {
handle_lcd_frame_sync();
}
if (events & EVT_MASK_ENCODER) {
handle_encoder();
}
@@ -328,17 +326,16 @@ ui::Widget* EventDispatcher::getFocusedWidget() {
return context.focus_manager().focus_widget();
}
void EventDispatcher::handle_lcd_frame_sync() {
void EventDispatcher::handle_lcd_frame_sync(bool screen_on) {
bool waiting_for_frame = this->waiting_for_frame;
DisplayFrameSyncMessage message;
DisplayFrameSyncMessage message; // send framesync msg all the time, bc some apps relay on it
message_map.send(&message);
static_cast<ui::SystemView*>(top_widget)->paint_overlay();
painter.paint_widget_tree(top_widget);
portapack::backlight()->on();
if (screen_on) { // only draw when screen is on
static_cast<ui::SystemView*>(top_widget)->paint_overlay();
painter.paint_widget_tree(top_widget);
portapack::backlight()->on();
}
if (waiting_for_frame)
this->waiting_for_frame = false;
}
+1 -1
View File
@@ -133,7 +133,7 @@ class EventDispatcher {
void on_keyboard_event(ui::KeyboardEvent event);
// void blink_timer();
void handle_lcd_frame_sync();
void handle_lcd_frame_sync(bool screen_on);
void handle_switches();
void handle_encoder();
void handle_touch();
+6 -6
View File
@@ -67,18 +67,18 @@ class ACARSAppView : public View {
uint32_t packet_counter{0};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(24), 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
{UI_POS_X(0), 0 * 8},
nav_};
Checkbox check_log{
{16 * 8, 1 * 16},
+2 -2
View File
@@ -62,7 +62,7 @@ class ADSBPositionView : public OptionTabView {
GeoPos::HIDDEN};
Button button_set_map{
{8 * 8, 6 * 16, 14 * 8, 2 * 16},
{UI_POS_X_CENTER(14), 6 * 16, 14 * 8, 2 * 16},
"Set from map"};
};
@@ -244,7 +244,7 @@ class ADSBTxView : public View {
"-"};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
1000000,
0};
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2025 Pezsma
*
* 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_adult_toys_controller.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::adult_toys_controller {
void initialize_app(::ui::NavigationView& nav) {
nav.push<AdultToysView>();
}
} // namespace ui::external_app::adult_toys_controller
extern "C" {
__attribute__((section(".external_app.app_adult_toys_controller.application_information"), used)) application_information_t _application_information_adult_toys_controller = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::adult_toys_controller::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Adult Toys",
/*.bitmap_data = */ {
0x00,
0x00,
0x30,
0x0C,
0x78,
0x1E,
0x7C,
0x3E,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFC,
0x3F,
0xFC,
0x3F,
0xF8,
0x1F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x80,
0x01,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_btle_tx */ {'P', 'B', 'T', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,373 @@
#include "ui_adult_toys_controller.hpp"
#include "ui_modemsetup.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_navigation.hpp"
using namespace portapack;
using namespace modems;
using namespace ui;
namespace ui::external_app::adult_toys_controller {
void AdultToysView::focus() {
button_on.focus();
}
AdultToysView::AdultToysView(NavigationView& nav)
: nav_{nav} {
add_children({
&button_on,
&btn_adult,
&btn_child,
&btn_mode_decrease,
&btn_mode_increase,
&btn_pcknum_inc,
&btn_pcknum_dec,
&chk_mode_rnd,
&chk_mode_play,
&chk_mode_stop,
&chk_mode_ncnt,
&chk_mode_infinite,
&field_frequency,
&txt_selected_mode,
&options_target,
&labels,
&message_num,
&tx_view,
&screen_message_1,
&screen_message_2,
&screen_message_3,
&screen_message_4,
&screen_message_5,
&screen_message_6,
&screen_message_7,
&screen_message_8,
&screen_message_9,
&screen_message_10,
});
btn_adult.on_select = [this](Button&) {
start_screen = true;
hidden_program(!start_screen);
};
btn_child.on_select = [this](Button&) {
nav_.pop();
};
button_on.on_select = [this](Button&) {
if (play_running || stop_running) {
if (button_onstate) {
button_onstate = false;
stop();
button_on.set_text(LanguageHelper::currentMessages[LANG_START]);
} else {
button_onstate = true;
printCurrentModes();
start();
button_on.set_text(LanguageHelper::currentMessages[LANG_STOP]);
}
} // if play_running || stop_running
};
btn_mode_decrease.on_select = [this](Button&) {
if (toy_packet > 0) {
toy_packet--;
printCurrentModes();
}
};
btn_mode_increase.on_select = [this](Button&) {
if (stop_running || play_running) {
if (play_running && (toy_packet < max_plays - 1)) {
toy_packet++;
} else {
if (stop_running && toy_packet < max_stops - 1) {
toy_packet++;
}
}
printCurrentModes();
}
};
btn_pcknum_inc.on_select = [this](Button&) {
if (!button_onstate) {
chk_mode_ncnt.set_value(true);
if (n_message_set < n_message_max) {
n_message_set++;
} else {
n_message_set = 0;
}
message_num.set_value(n_message_set);
n_message_current = n_message_set;
}
};
btn_pcknum_dec.on_select = [this](Button&) {
if (!button_onstate) {
chk_mode_ncnt.set_value(true);
if (n_message_set > 0) {
n_message_set--;
} else {
n_message_set = n_message_max;
}
message_num.set_value(n_message_set);
n_message_current = n_message_set;
}
};
message_num.on_change = [this](int32_t v) {
if (!button_onstate) {
chk_mode_ncnt.set_value(true);
n_message_set = static_cast<uint16_t>(v);
n_message_current = n_message_set;
} else {
if (!button_onstate) message_num.set_value(n_message_current);
}
};
chk_mode_rnd.on_select = [this](Checkbox&, bool v) {
random_running = v;
};
chk_mode_play.on_select = [this](Checkbox&, bool v) {
play_running = v;
if (play_running && stop_running) {
toy_packet = 0;
stop_running = false;
chk_mode_stop.set_value(stop_running);
}
if (play_running) printCurrentModes();
};
chk_mode_stop.on_select = [this](Checkbox&, bool v) {
stop_running = v;
if (play_running && stop_running) {
toy_packet = 0;
play_running = false;
chk_mode_play.set_value(play_running);
}
if (stop_running) printCurrentModes();
};
chk_mode_ncnt.on_select = [this](Checkbox&, bool v) {
n_message_running = v;
if (n_message_running && inf_message_running) {
inf_message_running = false;
chk_mode_infinite.set_value(inf_message_running);
}
};
chk_mode_infinite.on_select = [this](Checkbox&, bool v) {
inf_message_running = v;
if (n_message_running && inf_message_running) {
n_message_running = false;
chk_mode_ncnt.set_value(n_message_running);
}
};
options_target.on_change = [this](size_t, int32_t i) {
target = (uint8_t)i;
};
chk_mode_infinite.set_value(true);
chk_mode_play.set_value(true);
}
void AdultToysView::hidden_program(bool hide) {
options_target.hidden(hide);
labels.hidden(hide);
button_on.hidden(hide);
btn_mode_decrease.hidden(hide);
btn_mode_increase.hidden(hide);
btn_pcknum_inc.hidden(hide);
btn_pcknum_dec.hidden(hide);
chk_mode_rnd.hidden(hide);
chk_mode_play.hidden(hide);
chk_mode_stop.hidden(hide);
chk_mode_ncnt.hidden(hide);
chk_mode_infinite.hidden(hide);
field_frequency.hidden(hide);
txt_selected_mode.hidden(hide);
message_num.hidden(hide);
tx_view.hidden(hide);
btn_adult.hidden(!hide);
btn_child.hidden(!hide);
screen_message_1.hidden(!hide);
screen_message_2.hidden(!hide);
screen_message_3.hidden(!hide);
screen_message_4.hidden(!hide);
screen_message_5.hidden(!hide);
screen_message_6.hidden(!hide);
screen_message_7.hidden(!hide);
screen_message_8.hidden(!hide);
screen_message_9.hidden(!hide);
screen_message_10.hidden(!hide);
set_dirty();
if (hide)
btn_child.focus();
else
button_on.focus();
}
void AdultToysView::on_show() {
hidden_program(!start_screen);
}
void AdultToysView::createPacket(bool regenerate) {
if (regenerate) randomizeMac();
randomChn();
if (target == 1 || 1) { // now only lovespouse supported, in the future, need to separee them
uint32_t mode = play_running ? plays[toy_packet].value : stops[toy_packet].value;
const uint8_t size = 22;
uint8_t packet[size];
uint8_t i = 0;
// 1. AD Flags
packet[i++] = 2;
packet[i++] = 0x01;
packet[i++] = 0x1A;
// 2. Manufacturer Specific
packet[i++] = 14;
packet[i++] = 0xFF;
packet[i++] = 0xFF;
packet[i++] = 0x00;
packet[i++] = 0x6D;
packet[i++] = 0xB6;
packet[i++] = 0x43;
packet[i++] = 0xCE;
packet[i++] = 0x97;
packet[i++] = 0xFE;
packet[i++] = 0x42;
packet[i++] = 0x7C;
packet[i++] = (mode >> 0x10) & 0xFF;
packet[i++] = (mode >> 0x8) & 0xFF;
packet[i++] = (mode >> 0x0) & 0xFF;
// 3. Service UUID List
packet[i++] = 3;
packet[i++] = 0x03;
packet[i++] = 0x8F;
packet[i++] = 0xAE;
std::string res = to_string_hex_array(packet, i);
memset(advertisementData, 0, sizeof(advertisementData));
std::copy(res.begin(), res.end(), advertisementData);
}
}
AdultToysView::~AdultToysView() {
stop();
}
void AdultToysView::stop() {
transmitter_model.disable();
baseband::shutdown();
}
void AdultToysView::start() {
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
on_tx_progress(true);
}
void AdultToysView::reset() {
stop();
start();
}
uint8_t AdultToysView::randomize(uint8_t max) {
return static_cast<uint8_t>(rand() % max);
}
void AdultToysView::printCurrentModes() {
uint8_t max_val = play_running ? max_plays : max_stops;
auto name = play_running ? plays[toy_packet].name : stops[toy_packet].name;
std::string current = to_string_dec_uint(toy_packet + 1);
std::string max_val_str = to_string_dec_uint(max_val);
std::string result_str = current;
result_str += "/";
result_str += max_val_str;
result_str += " ";
result_str += name;
txt_selected_mode.set(result_str.c_str());
}
uint64_t AdultToysView::get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
void AdultToysView::randomizeMac() {
const char hexDigits[] = "0123456789ABCDEF";
// Generate 12 random hexadecimal characters
for (int i = 0; i < 12; ++i) {
int randomIndex = rand() % 16;
mac[i] = hexDigits[randomIndex];
}
mac[12] = '\0'; // Null-terminate the string
}
void AdultToysView::randomChn() {
channel_number = 37 + std::rand() % (39 - 37 + 1);
field_frequency.set_value(get_freq_by_channel_number(channel_number));
}
void AdultToysView::on_tx_progress(const bool done) {
if (!done) return;
if (!button_onstate) return;
bool regenerate = (counter++ % 600) == 0;
if (random_running && regenerate) {
toy_packet = randomize(play_running ? max_plays : max_stops);
}
createPacket(regenerate);
printCurrentModes();
if (n_message_running) {
if (n_message_current > 0) {
n_message_current--;
message_num.set_value(n_message_current);
} else {
stop();
message_num.set_value(n_message_set);
button_on.set_text(LanguageHelper::currentMessages[LANG_START]);
button_onstate = false;
n_message_current = n_message_set;
return;
}
} // if n_message
baseband::set_btletx(channel_number, mac, advertisementData, pduType);
}
} // namespace ui::external_app::adult_toys_controller
@@ -0,0 +1,245 @@
/*
* Copyright (C) 2025 Pezsma
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
// Code from https://github.com/Next-Flip/Momentum-Apps/blob/89d493daa1ea0cb86906fa0042c41dd4edc22a6a/ble_spam/protocols/lovespouse.c#L93
// Thanks for the work of the original creators!
#ifndef __UI_ADULT_TOYS_H__
#define __UI_ADULT_TOYS_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
using namespace ui;
namespace ui::external_app::adult_toys_controller {
enum PKT_TYPE {
PKT_TYPE_INVALID_TYPE,
PKT_TYPE_RAW,
PKT_TYPE_DISCOVERY,
PKT_TYPE_IBEACON,
PKT_TYPE_ADV_IND,
PKT_TYPE_ADV_DIRECT_IND,
PKT_TYPE_ADV_NONCONN_IND,
PKT_TYPE_ADV_SCAN_IND,
PKT_TYPE_SCAN_REQ,
PKT_TYPE_SCAN_RSP,
PKT_TYPE_CONNECT_REQ,
PKT_TYPE_LL_DATA,
PKT_TYPE_LL_CONNECTION_UPDATE_REQ,
PKT_TYPE_LL_CHANNEL_MAP_REQ,
PKT_TYPE_LL_TERMINATE_IND,
PKT_TYPE_LL_ENC_REQ,
PKT_TYPE_LL_ENC_RSP,
PKT_TYPE_LL_START_ENC_REQ,
PKT_TYPE_LL_START_ENC_RSP,
PKT_TYPE_LL_UNKNOWN_RSP,
PKT_TYPE_LL_FEATURE_REQ,
PKT_TYPE_LL_FEATURE_RSP,
PKT_TYPE_LL_PAUSE_ENC_REQ,
PKT_TYPE_LL_PAUSE_ENC_RSP,
PKT_TYPE_LL_VERSION_IND,
PKT_TYPE_LL_REJECT_IND,
PKT_TYPE_NUM_PKT_TYPE
};
class AdultToysView : public ui::View {
public:
AdultToysView(NavigationView& nav);
~AdultToysView();
void focus() override;
void on_show() override;
std::string title() const override {
return "Adult Toys";
};
struct LovespouseMode {
uint32_t value;
std::string name;
};
private:
NavigationView& nav_;
TxRadioState radio_state_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */
};
TxFrequencyField field_frequency{
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
TransmitterView2 tx_view{
{UI_POS_X(11), UI_POS_Y(0)},
/*short_ui*/ true};
app_settings::SettingsManager settings_{
"tx_adult_toys", app_settings::Mode::TX};
OptionsField options_target{
{UI_POS_X(6), UI_POS_Y(1)},
11,
{{"LoveSpouse", 1}}};
Button btn_adult{{UI_POS_X(0), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(10), UI_POS_HEIGHT(3)}, "adult"};
Button btn_child{{UI_POS_X_RIGHT(10), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(10), UI_POS_HEIGHT(3)}, "child"};
Button button_on{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
LanguageHelper::currentMessages[LANG_START]};
Button btn_mode_decrease{
{UI_POS_X(0), UI_POS_Y(13), UI_POS_WIDTH(3), UI_POS_HEIGHT(2)},
"<-"};
Button btn_mode_increase{
{UI_POS_X_RIGHT(3), UI_POS_Y(13), UI_POS_WIDTH(3), UI_POS_HEIGHT(2)},
"->"};
Text txt_selected_mode{{UI_POS_X_CENTER(22), UI_POS_Y(13.5), UI_POS_WIDTH(22), UI_POS_HEIGHT(1)}, ""};
Button btn_pcknum_inc{
{UI_POS_X(7), UI_POS_Y(9), UI_POS_WIDTH(2), UI_POS_HEIGHT(1.5)},
"+"};
Button btn_pcknum_dec{
{UI_POS_X(0), UI_POS_Y(9), UI_POS_WIDTH(2), UI_POS_HEIGHT(1.5)},
"-"};
NumberField message_num{{UI_POS_X(3), UI_POS_Y(9.2)}, 3, {0, 999}, 1, ' ', true};
Checkbox chk_mode_rnd{{UI_POS_X_RIGHT(15), UI_POS_Y(3)}, 12, "Random Msg.", false};
Checkbox chk_mode_play{{UI_POS_X_RIGHT(15), UI_POS_Y(5)}, 10, "Play Mode", false};
Checkbox chk_mode_stop{{UI_POS_X_RIGHT(15), UI_POS_Y(7)}, 10, "Stop Mode", false};
Checkbox chk_mode_ncnt{{UI_POS_X_RIGHT(15), UI_POS_Y(9)}, 7, "N Msg.", false};
Checkbox chk_mode_infinite{{UI_POS_X_RIGHT(15), UI_POS_Y(11)}, 10, "Inf. Msg.", false};
Text screen_message_1{{0, 0, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "The application is intended"};
Text screen_message_2{{0, 25, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "for controlling Love Spouse"};
Text screen_message_3{{0, 50, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "auxiliary tools/devices."};
Text screen_message_4{{0, 75, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "It is only for use by adults."};
Text screen_message_5{{0, 100, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "Are you an adult?"};
Text screen_message_6{{0, 140, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "To use the application, "};
Text screen_message_7{{0, 165, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "I assume full responsibility"};
Text screen_message_8{{0, 190, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "for the \"I do not assume"};
Text screen_message_9{{0, 215, UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)}, "responsibility\"."};
Text screen_message_10{{UI_POS_X_RIGHT(16), 230, UI_POS_WIDTH(16), UI_POS_HEIGHT(1)}, "(The developer)"};
Labels labels{
{{UI_POS_X(0), UI_POS_Y(1)}, "Type:", Theme::getInstance()->fg_light->foreground}};
bool button_onstate{false};
bool play_running{false};
bool stop_running{false};
bool random_running{false};
bool n_message_running{false};
bool inf_message_running{false};
bool start_screen{false};
uint8_t target = 1; // lovespouse
char mac[13] = "010203040506";
uint8_t channel_number = 37;
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
uint8_t toy_packet = 0;
uint16_t n_message_max = 999;
uint16_t n_message_set = 0;
uint16_t n_message_current = 0;
static const size_t max_plays = 27;
static const size_t max_stops = 3;
uint16_t counter = 599;
void createPacket(bool regenerate);
void start();
void stop();
void reset();
void printCurrentModes();
void on_tx_progress(const bool done);
void hidden_program(bool hide);
void randomizeMac();
void randomChn();
uint8_t randomize(uint8_t max);
uint64_t get_freq_by_channel_number(uint8_t channel_number);
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
}};
LovespouseMode plays[max_plays] = {
{0xE49C6C, "Classic 1"},
{0xE7075E, "Classic 2"},
{0xE68E4F, "Classic 3"},
{0xE1313B, "Classic 4"},
{0xE0B82A, "Classic 5"},
{0xE32318, "Classic 6"},
{0xE2AA09, "Classic 7"},
{0xED5DF1, "Classic 8"},
{0xECD4E0, "Classic 9"},
{0xD41F5D, "Independent 1-1"},
{0xD7846F, "Independent 1-2"},
{0xD60D7E, "Independent 1-3"},
{0xD1B20A, "Independent 1-4"},
{0xD0B31B, "Independent 1-5"},
{0xD3A029, "Independent 1-6"},
{0xD22938, "Independent 1-7"},
{0xDDDEC0, "Independent 1-8"},
{0xDC57D1, "Independent 1-9"},
{0xA4982E, "Independent 2-1"},
{0xA7031C, "Independent 2-2"},
{0xA68A0D, "Independent 2-3"},
{0xA13579, "Independent 2-4"},
{0xA0BC68, "Independent 2-5"},
{0xA3275A, "Independent 2-6"},
{0xA2AE4B, "Independent 2-7"},
{0xAD59B3, "Independent 2-8"},
{0xACD0A2, "Independent 2-9"},
};
LovespouseMode stops[max_stops] = {
{0xE5157D, "Classic Stop"},
{0xD5964C, "Independent 1 Stop"},
{0xA5113F, "Independent 2 Stop"},
};
};
}; // namespace ui::external_app::adult_toys_controller
#endif // ui_adult_toys_controller_hpp
+9 -9
View File
@@ -73,31 +73,31 @@ class AFSKRxView : public View {
bool logging{false};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(24), 4}};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{screen_width - 2 * 8, UI_POS_Y(0)}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
Checkbox check_log{
{0 * 8, 1 * 16},
{UI_POS_X(0), 1 * 16},
3,
LanguageHelper::currentMessages[LANG_LOG],
false};
Text text_debug{
{0 * 8, 12 + 2 * 16, screen_width, 16},
{UI_POS_X(0), 12 + 2 * 16, screen_width, 16},
LanguageHelper::currentMessages[LANG_DEBUG]};
Button button_modem_setup{
+7 -7
View File
@@ -58,8 +58,8 @@ class AnalogTvView : public View {
app_settings::SettingsManager settings_{
"rx_tv", app_settings::Mode::RX};
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
const Rect options_view_rect{UI_POS_X(0), 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{UI_POS_X(0), 1 * 16, 18 * 8, 1 * 16};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
@@ -71,17 +71,17 @@ class AnalogTvView : public View {
{21 * 8, 10, 6 * 8, 4}};
RxFrequencyField field_frequency{
{5 * 8, 0 * 16},
{5 * 8, UI_POS_Y(0)},
nav_};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
OptionsField options_modulation{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
4,
{
{"TV ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
@@ -90,7 +90,7 @@ class AnalogTvView : public View {
}};
AudioVolumeField field_volume{
{27 * 8, 0 * 16}};
{27 * 8, UI_POS_Y(0)}};
std::unique_ptr<Widget> options_widget{};
@@ -34,7 +34,7 @@ using namespace portapack;
#include <cmath>
#include <array>
namespace ui {
namespace ui::external_app::analogtv {
namespace tv {
/* TimeScopeView******************************************************/
@@ -235,4 +235,4 @@ void TVWidget::on_audio_spectrum() {
}
} /* namespace tv */
} /* namespace ui */
} // namespace ui::external_app::analogtv
@@ -33,7 +33,7 @@
#include <cstdint>
#include <cstddef>
namespace ui {
namespace ui::external_app::analogtv {
namespace tv {
class TimeScopeView : public View {
@@ -50,11 +50,11 @@ class TimeScopeView : public View {
int16_t audio_spectrum[128]{0};
/*Labels labels {
{ { 6 * 8, 0 * 16 }, "Hz", Theme::getInstance()->fg_light->foreground }
{ { 6 * 8,UI_POS_Y(0) }, "Hz", Theme::getInstance()->fg_light->foreground }
};*/
/*
NumberField field_frequency {
{ 0 * 8, 0 * 16 },
{ 0 * 8,UI_POS_Y(0) },
5,
{ 0, 48000 },
48000 / 240,
@@ -108,7 +108,7 @@ class TVWidget : public View {
void paint(Painter& painter) override;
NumberField field_xcorr{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
5,
{0, 128},
1,
@@ -117,7 +117,7 @@ class TVWidget : public View {
private:
void update_widgets_rect();
const Rect audio_spectrum_view_rect{0 * 8, 0 * 16, screen_width, 2 * 16 + 20};
const Rect audio_spectrum_view_rect{UI_POS_X(0), UI_POS_Y(0), screen_width, 2 * 16 + 20};
static constexpr Dim audio_spectrum_height = 16 * 2 + 20;
static constexpr Dim scale_height = 20;
@@ -167,6 +167,6 @@ class TVWidget : public View {
};
} /* namespace tv */
} /* namespace ui */
} // namespace ui::external_app::analogtv
#endif /*__UI_TV_H__*/
@@ -89,7 +89,7 @@ class WhipCalcView : public View {
{0, 6 * 16, screen_width, 160}};
Button button_exit{
{72, 17 * 16, 96, 32},
{UI_POS_X_RIGHT(12), UI_POS_Y_BOTTOM(3), 96, 32},
"Back"};
};
+40 -27
View File
@@ -1,5 +1,5 @@
/*
* copyleft spammingdramaqueen
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
*
* This file is part of PortaPack.
*
@@ -50,7 +50,7 @@ AppManagerView::AppManagerView(NavigationView& nav)
&button_set_cancel_autostart,
&button_clean_autostart});
menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
menu_view.set_parent_rect({0, 2 * 8, screen_width, UI_POS_HEIGHT_REMAINING(7)});
menu_view.on_highlight = [this]() {
if (menu_view.highlighted_index() >= app_list_index) {
@@ -62,19 +62,24 @@ AppManagerView::AppManagerView(NavigationView& nav)
auto app_name = get_app_info(menu_view.highlighted_index(), true);
auto app_id = get_app_info(menu_view.highlighted_index(), false);
info += "Hidden:";
if (!app_name.empty()) {
info += "Hidden:";
if (is_blacklisted(app_name)) {
info += "Yes ";
} else {
info += "No ";
if (is_blacklisted(app_name)) {
info += "Yes ";
} else {
info += "No ";
}
}
info += "Autostart:";
if (is_autostart_app(app_id)) {
info += "Yes";
} else {
info += "No";
if (!app_id.empty()) {
info += "Autostart:";
if (is_autostart_app(app_id)) {
info += "Yes";
} else {
info += "No";
}
}
if (info.empty()) {
@@ -82,6 +87,8 @@ AppManagerView::AppManagerView(NavigationView& nav)
}
text_app_info.set(info);
button_hide_unhide.set_focusable(!app_name.empty());
button_set_cancel_autostart.set_focusable(!app_id.empty());
};
button_hide_unhide.on_select = [this](Button&) {
@@ -120,27 +127,23 @@ void AppManagerView::refresh_list() {
};
for (auto& app : NavigationView::appList) {
if (app.id == nullptr) continue;
app_list_index++;
menu_view.add_item({app.displayName + std::string(is_autostart_app(app.id) ? padding(app.displayName) : ""),
menu_view.add_item({app.displayName + std::string((app.id != nullptr && is_autostart_app(app.id)) ? padding(app.displayName) : ""),
app.iconColor,
app.icon,
[this, app_id = std::string(app.id)](KeyEvent) {
[this, app_id = std::string(app.id != nullptr ? app.id : "")](KeyEvent) {
button_hide_unhide.focus();
}});
}
ExternalItemsMenuLoader::load_all_external_items_callback([this, &index, &padding](ui::AppInfoConsole& app) {
if (app.appCallName == nullptr) return;
app_list_index++;
menu_view.add_item({app.appFriendlyName + std::string(is_autostart_app(app.appCallName) ? padding(app.appFriendlyName) : ""),
menu_view.add_item({app.appFriendlyName + std::string((app.appCallName != nullptr && is_autostart_app(app.appCallName)) ? padding(app.appFriendlyName) : ""),
ui::Color::light_grey(),
&bitmap_icon_sdcard,
[this, app_id = std::string(app.appCallName)](KeyEvent) {
[this, app_id = std::string(app.appCallName != nullptr ? app.appCallName : "")](KeyEvent) {
button_hide_unhide.focus();
}});
});
@@ -156,19 +159,29 @@ void AppManagerView::hide_app() {
std::vector<std::string> blacklist;
get_blacklist(blacklist);
blacklist.push_back(get_app_info(menu_view.highlighted_index(), true));
auto app_name = get_app_info(menu_view.highlighted_index(), true);
if (app_name.empty()) return;
blacklist.push_back(app_name);
write_blacklist(blacklist);
}
void AppManagerView::unhide_app() {
std::vector<std::string> blacklist;
get_blacklist(blacklist);
blacklist.erase(std::remove(blacklist.begin(), blacklist.end(), get_app_info(menu_view.highlighted_index(), true)), blacklist.end());
auto app_name = get_app_info(menu_view.highlighted_index(), true);
if (app_name.empty()) return;
blacklist.erase(std::remove(blacklist.begin(), blacklist.end(), app_name), blacklist.end());
write_blacklist(blacklist);
}
void AppManagerView::hide_unhide_app() {
if (is_blacklisted(get_app_info(menu_view.highlighted_index(), true)))
auto app_name = get_app_info(menu_view.highlighted_index(), true);
if (app_name.empty()) return;
if (is_blacklisted(app_name))
unhide_app();
else
hide_app();
@@ -218,6 +231,7 @@ void AppManagerView::set_auto_start() {
if (app_index >= app_list_index) return;
auto id_aka_app_call_name = get_app_info(app_index, false);
if (id_aka_app_call_name.empty()) return;
autostart_app = id_aka_app_call_name;
@@ -234,6 +248,7 @@ void AppManagerView::set_unset_autostart_app() {
if (app_index >= app_list_index) return;
auto id_aka_friendly_name = get_app_info(app_index, false);
if (id_aka_friendly_name.empty()) return;
if (is_autostart_app(id_aka_friendly_name))
unset_auto_start();
@@ -254,17 +269,15 @@ std::string AppManagerView::get_app_info(uint16_t index, bool is_display_name) {
std::string result;
for (auto& app : NavigationView::appList) {
if (app.id == nullptr) continue;
if (current_index == index) {
return is_display_name ? std::string(app.displayName) : std::string(app.id);
return is_display_name ? std::string(app.displayName != nullptr ? app.displayName : "") : std::string(app.id != nullptr ? app.id : "");
}
current_index++;
}
ExternalItemsMenuLoader::load_all_external_items_callback([&current_index, &index, &result, &is_display_name](ui::AppInfoConsole& app) {
if (app.appCallName == nullptr) return;
if (current_index == index) {
result = is_display_name ? app.appFriendlyName : app.appCallName;
result = is_display_name ? (app.appFriendlyName != nullptr ? app.appFriendlyName : "") : (app.appCallName != nullptr ? app.appCallName : "");
}
current_index++;
});
@@ -1,5 +1,5 @@
/*
* copyleft spammingdramaqueen
* copyleft 2024 zxkmm AKA zix aka sommermorgentraum
*
* This file is part of PortaPack.
*
@@ -43,28 +43,28 @@ class AppManagerView : public View {
uint16_t app_list_index{0};
Labels labels{
{{0 * 8, 0 * 16}, "App list:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(0)}, "App list:", Theme::getInstance()->fg_light->foreground}};
MenuView menu_view{};
Text text_app_info{
{0, 27 * 8, screen_width, 16},
{0, UI_POS_Y_BOTTOM(6), screen_width, UI_POS_HEIGHT(1)},
"Highlight an app"};
Button button_hide_unhide{
{0, 29 * 8, screen_width / 2 - 1, 32},
{0, UI_POS_Y_BOTTOM(5), screen_width / 2 - 1, UI_POS_HEIGHT(2)},
"Hide/Show"};
Button button_clean_hide{
{screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
{screen_width / 2 + 2, UI_POS_Y_BOTTOM(5), screen_width / 2 - 2, UI_POS_HEIGHT(2)},
"Clean Hidden"};
Button button_set_cancel_autostart{
{0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
{0, screen_height - 32 - 16, screen_width / 2 - 1, UI_POS_HEIGHT(2)},
"Set Autostart"};
Button button_clean_autostart{
{screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
{screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, UI_POS_HEIGHT(2)},
"Del Autostart"};
std::string get_app_info(uint16_t index, bool is_display_name);
+5 -5
View File
@@ -44,13 +44,13 @@ class AudioTestView : public View {
bool beep{false};
Labels labels{
{{7 * 8, 3 * 16}, "Audio Beep Test", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Sample Rate (Hz):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(16), 3 * 16}, "Audio Beep Test", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 6 * 16}, "Sample Rate (Hz):", Theme::getInstance()->fg_light->foreground},
{{25 * 8, 7 * 16}, "Step:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Frequency (Hz):", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 10 * 16}, "Duration (ms):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "Frequency (Hz):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 10 * 16}, "Duration (ms):", Theme::getInstance()->fg_light->foreground},
{{25 * 8, 10 * 16}, "0=con", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 12 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 12 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_sample_rate{
{18 * 8, 6 * 16},
+75 -127
View File
@@ -24,25 +24,25 @@ constexpr uint32_t BLUE_TEAM_ADDRESS = BATTLESHIP_BASE_ADDRESS + 2;
BattleshipView::BattleshipView(NavigationView& nav)
: nav_{nav} {
CELL_SIZE = screen_width / GRID_SIZE;
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
add_children({&text_title, &text_subtitle,
&rect_radio_settings, &label_radio, &button_frequency,
//&rect_radio_settings, &rect_audio_settings, &rect_team_selection,
&label_radio, &button_frequency,
&label_rf_amp, &checkbox_rf_amp,
&label_lna, &field_lna,
&label_vga, &field_vga,
&label_tx_gain, &field_tx_gain,
&rect_audio_settings, &label_audio,
&label_audio,
&checkbox_sound, &label_volume, &field_volume,
&rect_team_selection, &label_team,
&label_team,
&button_red_team, &button_blue_team,
&rssi, &text_status, &text_score,
&rssi,
&button_rotate, &button_place, &button_fire, &button_menu});
// Hide in-game elements
rssi.hidden(true);
text_status.hidden(true);
text_score.hidden(true);
button_rotate.hidden(true);
button_place.hidden(true);
button_fire.hidden(true);
@@ -151,8 +151,8 @@ BattleshipView::BattleshipView(NavigationView& nav)
field_tx_gain.set_parent_rect({185, 118, 32, 16});
checkbox_sound.set_parent_rect({17, 187, 80, 24});
field_volume.set_parent_rect({165, 187, 32, 16});
button_red_team.set_parent_rect({25, 242, 85, 45});
button_blue_team.set_parent_rect({130, 242, 85, 45});
// button_red_team.set_parent_rect({25, 242, 85, 45});
// button_blue_team.set_parent_rect({130, 242, 85, 45});
// Make menu elements focusable
button_frequency.set_focusable(true);
@@ -191,11 +191,42 @@ void BattleshipView::init_game() {
enemy_grid[y][x] = CellState::EMPTY;
}
}
setup_ships();
update_score();
}
void BattleshipView::show_hide_menu(bool menu_vis) {
text_title.hidden(!menu_vis);
text_subtitle.hidden(!menu_vis);
// rect_radio_settings.hidden(!menu_vis); rect_audio_settings.hidden(!menu_vis); rect_team_selection.hidden(!menu_vis);
label_radio.hidden(!menu_vis);
button_frequency.hidden(!menu_vis);
label_rf_amp.hidden(!menu_vis);
checkbox_rf_amp.hidden(!menu_vis);
label_lna.hidden(!menu_vis);
field_lna.hidden(!menu_vis);
label_vga.hidden(!menu_vis);
field_vga.hidden(!menu_vis);
label_tx_gain.hidden(!menu_vis);
field_tx_gain.hidden(!menu_vis);
label_audio.hidden(!menu_vis);
checkbox_sound.hidden(!menu_vis);
label_volume.hidden(!menu_vis);
field_volume.hidden(!menu_vis);
label_team.hidden(!menu_vis);
button_red_team.hidden(!menu_vis);
button_blue_team.hidden(!menu_vis);
rssi.hidden(menu_vis);
button_rotate.hidden(menu_vis);
button_place.hidden(menu_vis);
button_menu.hidden(menu_vis);
// button_rotate.set_focusable(false); //no need, since can't focus on hidden
// button_place.set_focusable(false);
// button_menu.set_focusable(false);
set_dirty();
}
void BattleshipView::reset_game() {
transmitter_model.disable();
receiver_model.disable();
@@ -221,50 +252,7 @@ void BattleshipView::reset_game() {
update_score();
// Toggle visibility
bool menu_vis = true;
bool game_vis = false;
text_title.hidden(!menu_vis);
text_subtitle.hidden(!menu_vis);
rect_radio_settings.hidden(!menu_vis);
label_radio.hidden(!menu_vis);
button_frequency.hidden(!menu_vis);
label_rf_amp.hidden(!menu_vis);
checkbox_rf_amp.hidden(!menu_vis);
label_lna.hidden(!menu_vis);
field_lna.hidden(!menu_vis);
label_vga.hidden(!menu_vis);
field_vga.hidden(!menu_vis);
label_tx_gain.hidden(!menu_vis);
field_tx_gain.hidden(!menu_vis);
rect_audio_settings.hidden(!menu_vis);
label_audio.hidden(!menu_vis);
checkbox_sound.hidden(!menu_vis);
label_volume.hidden(!menu_vis);
field_volume.hidden(!menu_vis);
rect_team_selection.hidden(!menu_vis);
label_team.hidden(!menu_vis);
button_red_team.hidden(!menu_vis);
button_blue_team.hidden(!menu_vis);
rssi.hidden(!game_vis);
text_status.hidden(!game_vis);
text_score.hidden(!game_vis);
button_rotate.hidden(!game_vis);
button_place.hidden(!game_vis);
button_fire.hidden(!game_vis);
button_menu.hidden(!game_vis);
// Restore focusability
button_frequency.set_focusable(menu_vis);
checkbox_rf_amp.set_focusable(menu_vis);
field_lna.set_focusable(menu_vis);
field_vga.set_focusable(menu_vis);
field_tx_gain.set_focusable(menu_vis);
checkbox_sound.set_focusable(menu_vis);
field_volume.set_focusable(menu_vis);
button_red_team.set_focusable(menu_vis);
button_blue_team.set_focusable(menu_vis);
show_hide_menu(true);
button_frequency.focus();
set_dirty();
@@ -283,45 +271,10 @@ void BattleshipView::start_team(bool red) {
game_state = GameState::PLACING_SHIPS;
// Toggle visibility
bool menu_vis = false;
bool game_vis = true;
text_title.hidden(!menu_vis);
text_subtitle.hidden(!menu_vis);
rect_radio_settings.hidden(!menu_vis);
label_radio.hidden(!menu_vis);
button_frequency.hidden(!menu_vis);
label_rf_amp.hidden(!menu_vis);
checkbox_rf_amp.hidden(!menu_vis);
label_lna.hidden(!menu_vis);
field_lna.hidden(!menu_vis);
label_vga.hidden(!menu_vis);
field_vga.hidden(!menu_vis);
label_tx_gain.hidden(!menu_vis);
field_tx_gain.hidden(!menu_vis);
rect_audio_settings.hidden(!menu_vis);
label_audio.hidden(!menu_vis);
checkbox_sound.hidden(!menu_vis);
label_volume.hidden(!menu_vis);
field_volume.hidden(!menu_vis);
rect_team_selection.hidden(!menu_vis);
label_team.hidden(!menu_vis);
button_red_team.hidden(!menu_vis);
button_blue_team.hidden(!menu_vis);
rssi.hidden(!game_vis);
text_status.hidden(true);
text_score.hidden(true);
button_rotate.hidden(false);
button_place.hidden(false);
button_menu.hidden(false);
show_hide_menu(false);
current_status = "Place carrier (5)";
button_rotate.set_focusable(false);
button_place.set_focusable(false);
button_menu.set_focusable(false);
focus();
is_transmitting = true;
configure_radio_rx();
@@ -382,7 +335,7 @@ void BattleshipView::configure_radio_rx() {
}
void BattleshipView::paint(Painter& painter) {
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
painter.fill_rectangle({0, 0, UI_POS_MAXWIDTH, UI_POS_MAXHEIGHT}, Color::black());
if (game_state == GameState::MENU) {
draw_menu_screen(painter);
@@ -426,24 +379,21 @@ void BattleshipView::paint(Painter& painter) {
}
Color team_color = is_red_team ? Color::red() : Color::blue();
painter.fill_rectangle({0, 5, 240, 16}, team_color);
painter.fill_rectangle({0, 5, screen_width, 16}, team_color);
auto style_white = Style{
.font = ui::font::fixed_8x16,
.background = team_color,
.foreground = Color::white()};
painter.draw_string({85, 5}, style_white, is_red_team ? "RED TEAM" : "BLUE TEAM");
painter.draw_string({UI_POS_X_CENTER(9), 5}, style_white, is_red_team ? "RED TEAM" : "BLUE TEAM");
auto style_status = Style{
.font = ui::font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white()};
painter.fill_rectangle({0, 21, 240, 16}, Color::black());
painter.draw_string({10, 21}, style_status, current_status);
if (game_state != GameState::MENU) {
painter.draw_string({170, 21}, style_status, current_score);
}
painter.fill_rectangle({0, 21, screen_width, 16}, Color::black());
painter.draw_string({5, 21}, style_status, current_status);
painter.draw_string({170, 21}, style_status, current_score);
if (game_state == GameState::PLACING_SHIPS) {
draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, my_grid, true);
@@ -452,35 +402,35 @@ void BattleshipView::paint(Painter& painter) {
}
} else if (game_state == GameState::MY_TURN) {
draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, enemy_grid, false, true);
painter.draw_string({10, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
"Enemy ships: " + to_string_dec_uint(enemy_ships_remaining));
painter.draw_string({0, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
"Enemy: " + to_string_dec_uint(enemy_ships_remaining));
} else if (game_state == GameState::OPPONENT_TURN || game_state == GameState::WAITING_FOR_OPPONENT) {
draw_grid(painter, GRID_OFFSET_X, GRID_OFFSET_Y + 5, my_grid, true);
painter.draw_string({10, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
"Your ships: " + to_string_dec_uint(ships_remaining));
painter.draw_string({0, GRID_OFFSET_Y + GRID_SIZE * CELL_SIZE + 10}, style_status,
"You: " + to_string_dec_uint(ships_remaining));
} else if (game_state == GameState::GAME_OVER) {
painter.draw_string({50, 150}, style_status, "Game Over!");
painter.draw_string({UI_POS_X_CENTER(11), 150}, style_status, "Game Over!");
painter.draw_string({30, 170}, style_status, current_status);
}
}
void BattleshipView::draw_menu_screen(Painter& painter) {
painter.draw_hline({12, 38}, 216, Color::dark_cyan());
painter.draw_hline({12, 20 + 16}, screen_width - 24, Color::dark_cyan());
painter.fill_rectangle({13, 41, 214, 116}, Color::dark_grey());
painter.draw_hline({12, 40}, 216, Color::cyan());
painter.draw_hline({12, 157}, 216, Color::cyan());
painter.fill_rectangle({13, 59, screen_width - 26, 116}, Color::dark_grey());
painter.draw_hline({12, 58}, screen_width - 24, Color::cyan());
painter.draw_hline({12, 157}, screen_width - 24, Color::cyan());
painter.fill_rectangle({13, 165, 214, 43}, Color::dark_grey());
painter.draw_hline({12, 164}, 216, Color::cyan());
painter.draw_hline({12, 208}, 216, Color::cyan());
painter.fill_rectangle({13, 165, screen_width - 24, 57}, Color::dark_grey());
painter.draw_hline({12, 164}, screen_width - 24, Color::cyan());
painter.draw_hline({12, 223}, screen_width - 24, Color::cyan());
painter.fill_rectangle({13, 218, 214, 73}, Color::dark_grey());
painter.draw_hline({12, 217}, 216, Color::cyan());
painter.draw_hline({12, 291}, 216, Color::cyan());
painter.fill_rectangle({13, 232, screen_width - 26, 67}, Color::dark_grey());
painter.draw_hline({12, 232}, screen_width - 24, Color::cyan());
painter.draw_hline({12, 300}, screen_width - 24, Color::cyan());
// Radio status indicator
Point indicator_pos{220, 53};
Point indicator_pos{UI_POS_MAXWIDTH - 20, 59};
if (is_transmitting) {
painter.fill_rectangle({indicator_pos, {6, 6}}, Color::red());
painter.draw_rectangle({indicator_pos.x() - 1, indicator_pos.y() - 1, 8, 8}, Color::light_grey());
@@ -490,7 +440,7 @@ void BattleshipView::draw_menu_screen(Painter& painter) {
}
}
void BattleshipView::draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid) {
void BattleshipView::draw_grid(Painter& painter, uint16_t grid_x, uint16_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid) {
painter.fill_rectangle({grid_x, grid_y, GRID_SIZE * CELL_SIZE, GRID_SIZE * CELL_SIZE},
Color::dark_blue());
@@ -501,26 +451,24 @@ void BattleshipView::draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y,
GRID_SIZE * CELL_SIZE, Color::grey());
}
for (uint8_t y = 0; y < GRID_SIZE; y++) {
for (uint8_t x = 0; x < GRID_SIZE; x++) {
draw_cell(painter, grid_x + x * CELL_SIZE + 1, grid_y + y * CELL_SIZE + 1,
grid[y][x], show_ships, is_offense_grid,
is_cursor_at(x, y, is_offense_grid));
for (uint16_t y = 0; y < GRID_SIZE; y++) {
for (uint16_t x = 0; x < GRID_SIZE; x++) {
draw_cell(painter, grid_x + x * CELL_SIZE + 1, grid_y + y * CELL_SIZE + 1, grid[y][x], show_ships, is_offense_grid, is_cursor_at(x, y, is_offense_grid));
}
}
}
void BattleshipView::draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor) {
void BattleshipView::draw_cell(Painter& painter, uint16_t cell_x, uint16_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor) {
Color cell_color = Color::dark_blue();
bool should_fill = false;
if (game_state == GameState::PLACING_SHIPS && !is_offense_grid && current_ship_index < 5) {
uint8_t ship_size = my_ships[current_ship_index].size();
for (uint8_t i = 0; i < ship_size; i++) {
uint8_t preview_x = placing_horizontal ? cursor_x + i : cursor_x;
uint8_t preview_y = placing_horizontal ? cursor_y : cursor_y + i;
uint8_t grid_x = (cell_x - 1) / CELL_SIZE;
uint8_t grid_y = (cell_y - GRID_OFFSET_Y - 6) / CELL_SIZE;
uint16_t preview_x = placing_horizontal ? cursor_x + i : cursor_x;
uint16_t preview_y = placing_horizontal ? cursor_y : cursor_y + i;
uint16_t grid_x = (cell_x - 1) / CELL_SIZE;
uint16_t grid_y = (cell_y - GRID_OFFSET_Y - 6) / CELL_SIZE;
if (grid_x == preview_x && grid_y == preview_y && preview_x < GRID_SIZE && preview_y < GRID_SIZE) {
return;
}
@@ -597,8 +545,8 @@ void BattleshipView::draw_ship_preview(Painter& painter) {
uint8_t y = placing_horizontal ? cursor_y : cursor_y + i;
if (x < GRID_SIZE && y < GRID_SIZE) {
uint8_t cell_x = GRID_OFFSET_X + x * CELL_SIZE + 1;
uint8_t cell_y = GRID_OFFSET_Y + 5 + y * CELL_SIZE + 1;
uint16_t cell_x = GRID_OFFSET_X + x * CELL_SIZE + 1;
uint16_t cell_y = GRID_OFFSET_Y + 5 + y * CELL_SIZE + 1;
Color preview_color = can_place ? Color::green() : Color::red();
+26 -25
View File
@@ -23,12 +23,9 @@
#include <array>
namespace ui::external_app::battleship {
#define GRID_SIZE 10
constexpr uint8_t GRID_SIZE = 10;
constexpr uint8_t CELL_SIZE = 24;
constexpr uint8_t GRID_OFFSET_X = 0;
constexpr uint8_t GRID_OFFSET_Y = 32;
namespace ui::external_app::battleship {
enum class ShipType : uint8_t {
CARRIER = 5,
@@ -103,6 +100,10 @@ class BattleshipView : public View {
private:
NavigationView& nav_;
uint16_t CELL_SIZE = 24;
uint16_t GRID_OFFSET_X = 0;
uint16_t GRID_OFFSET_Y = 32;
RxRadioState rx_radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
@@ -163,11 +164,11 @@ class BattleshipView : public View {
uint32_t last_address{0};
// UI Elements - Menu/Settings Screen
Text text_title{{60, 2, 120, 24}, "BATTLESHIP"};
Text text_subtitle{{40, 20, 160, 16}, "Naval Combat Game"};
Text text_title{{UI_POS_X_CENTER(11), 2, UI_POS_WIDTH(11), 16}, "BATTLESHIP"};
Text text_subtitle{{UI_POS_X_CENTER(18), 24, UI_POS_WIDTH(18), 16}, "Naval Combat Game"};
Rectangle rect_radio_settings{{12, 40, 216, 118}, Color::dark_grey()};
Text label_radio{{17, 45, 100, 16}, "RADIO SETUP"};
// Rectangle rect_radio_settings{{12, 40, 216, 118}, Color::dark_grey()};
Text label_radio{{UI_POS_X_CENTER(12), 45, UI_POS_WIDTH(12), 16}, "RADIO SETUP"};
ButtonWithEncoder button_frequency{{17, 65, 11 * 8, 20}, ""};
// Radio controls
@@ -183,25 +184,24 @@ class BattleshipView : public View {
Text label_tx_gain{{155, 118, 25, 16}, "TX:"};
NumberField field_tx_gain{{185, 118}, 2, {0, 47}, 8, ' '};
Rectangle rect_audio_settings{{12, 164, 216, 45}, Color::dark_grey()};
Text label_audio{{17, 169, 80, 16}, "AUDIO"};
Checkbox checkbox_sound{{17, 187}, 8, "Sound On", true};
// Rectangle rect_audio_settings{{12, 164, 216, 45}, Color::dark_grey()};
Text label_audio{{UI_POS_X_CENTER(6), 155, UI_POS_WIDTH(6), 16}, "AUDIO"};
Checkbox checkbox_sound{{17, 177}, 8, "Sound On", true};
Text label_volume{{110, 187, 50, 16}, "Volume:"};
AudioVolumeField field_volume{{165, 187}};
AudioVolumeField field_volume{{165, 177}};
Rectangle rect_team_selection{{12, 217, 216, 75}, Color::dark_grey()};
Text label_team{{17, 222, 110, 16}, "SELECT TEAM"};
Button button_red_team{{25, 242, 85, 45}, "RED\nTEAM"};
Button button_blue_team{{130, 242, 85, 45}, "BLUE\nTEAM"};
// Rectangle rect_team_selection{{12, 217, 216, 75}, Color::dark_grey()};
Text label_team{{17, 220, 8 * 12, 16}, "SELECT TEAM"};
Button button_red_team{{UI_POS_X_CENTER(10) - UI_POS_WIDTH(7), 237, UI_POS_WIDTH(10), 45}, "RED\nTEAM"};
Button button_blue_team{{UI_POS_X_CENTER(10) + UI_POS_WIDTH(7), 237, UI_POS_WIDTH(10), 45}, "BLUE\nTEAM"};
// In-game UI elements
RSSI rssi{{21 * 8, 0, 6 * 8, 4}};
Text text_status{{10, 16, 220, 16}, ""};
RSSI rssi{{UI_POS_X_RIGHT(6), 0, 6 * 8, 4}};
Text text_score{{170, 16, 60, 16}, ""};
Button button_rotate{{10, 265, 65, 32}, "Rotate"};
Button button_place{{82, 265, 65, 32}, "Place"};
Button button_fire{{82, 265, 65, 32}, "Fire!"};
Button button_menu{{155, 265, 65, 32}, "Menu"};
Button button_rotate{{5, UI_POS_Y_BOTTOM(3), 65, 32}, "Rotate"};
Button button_place{{UI_POS_X_CENTER(6), UI_POS_Y_BOTTOM(3), 65, 32}, "Place"};
Button button_fire{{UI_POS_X_RIGHT(17), UI_POS_Y_BOTTOM(3), 65, 32}, "Fire!"};
Button button_menu{{UI_POS_X_RIGHT(8), UI_POS_Y_BOTTOM(3), 65, 32}, "Menu"};
// Methods
void init_game();
@@ -209,8 +209,8 @@ class BattleshipView : public View {
void start_team(bool red);
void setup_ships();
void draw_menu_screen(Painter& painter);
void draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
void draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
void draw_grid(Painter& painter, uint16_t grid_x, uint16_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
void draw_cell(Painter& painter, uint16_t cell_x, uint16_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
void draw_ship_preview(Painter& painter);
void place_ship();
bool can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal);
@@ -218,6 +218,7 @@ class BattleshipView : public View {
void process_shot(uint8_t x, uint8_t y);
void update_score();
bool is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid);
void show_hide_menu(bool menu_vis);
void send_message(const GameMessage& msg);
void process_message(const std::string& message);
@@ -21,7 +21,8 @@
*/
#include "bht.hpp"
#include "portapack_persistent_memory.hpp"
namespace ui::external_app::bht_tx {
size_t gen_message_ep(uint8_t city_code, size_t family_code_ep, uint32_t relay_number, uint32_t relay_state) {
size_t c;
@@ -152,3 +153,5 @@ std::string ccir_to_ascii(uint8_t* ccir) {
return ascii;
}
} // namespace ui::external_app::bht_tx
@@ -28,6 +28,8 @@
using namespace encoders;
namespace ui::external_app::bht_tx {
#define XY_TONE_DURATION ((TONES_SAMPLERATE * 0.1) - 1) // 100ms
#define XY_SILENCE (TONES_SAMPLERATE * 0.4) // 400ms
#define XY_TONE_COUNT 20
@@ -47,3 +49,5 @@ size_t gen_message_ep(uint8_t city_code, size_t family_code_ep, uint32_t relay_s
std::string gen_message_xy(const std::string& code);
std::string gen_message_xy(size_t header_code_a, size_t header_code_b, size_t city_code, size_t family_code, bool subfamily_wc, size_t subfamily_code, bool id_wc, size_t receiver_code, size_t relay_state_A, size_t relay_state_B, size_t relay_state_C, size_t relay_state_D);
std::string ccir_to_ascii(uint8_t* ccir);
} // namespace ui::external_app::bht_tx
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 EPIRB Decoder Implementation
*
* 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_bht_tx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::bht_tx {
void initialize_app(ui::NavigationView& nav) {
nav.push<BHTView>();
}
} // namespace ui::external_app::bht_tx
extern "C" {
__attribute__((section(".external_app.app_bht_tx.application_information"), used)) application_information_t _application_information_bht_tx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::bht_tx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "BHT TX",
/*.bitmap_data = */ {
0x00,
0x00,
0xE0,
0x07,
0xF8,
0x08,
0x9C,
0x07,
0x0C,
0x00,
0x8E,
0x0A,
0x46,
0x12,
0x26,
0x22,
0x06,
0x02,
0x06,
0x00,
0x06,
0x00,
0x06,
0x00,
0x06,
0x00,
0x06,
0x00,
0x06,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_tones */ {'P', 'T', 'O', 'N'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -28,7 +28,7 @@
using namespace portapack;
namespace ui {
namespace ui::external_app::bht_tx {
void BHTView::focus() {
tx_view.focus();
@@ -40,8 +40,7 @@ void BHTView::start_tx() {
transmitter_model.set_baseband_bandwidth(1750000);
if (target_system == XYLOS) {
baseband::run_image(portapack::spi_flash::image_tag_tones);
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
view_xylos.generate_message();
// if (tx_mode == SINGLE) {
@@ -339,4 +338,4 @@ void XylosView::focus() {
field_city.focus();
}
} /* namespace ui */
} // namespace ui::external_app::bht_tx
@@ -35,7 +35,7 @@
#include "radio_state.hpp"
#include "portapack.hpp"
namespace ui {
namespace ui::external_app::bht_tx {
class XylosView : public View {
public:
@@ -207,7 +207,7 @@ class BHTView : public View {
"Scan"};
Checkbox checkbox_flashing{
{16 * 8, 25 * 8},
{UI_POS_X(16), 25 * 8},
8,
"Flashing"};
NumberField field_speed{
@@ -218,11 +218,11 @@ class BHTView : public View {
' '};
ProgressBar progressbar{
{0 * 8, 29 * 8, screen_width, 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(5), screen_width, 16},
};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
10000,
12};
@@ -234,4 +234,4 @@ class BHTView : public View {
}};
};
} /* namespace ui */
} // namespace ui::external_app::bht_tx

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