Compare commits

...

124 Commits

Author SHA1 Message Date
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
gullradriel 4c5275247e update version (#2735)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-11 15:57:04 +02:00
gullradriel a0185d28eb update submodule (#2734)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-09 21:33:38 +02:00
RocketGod 2500df310f Add radio settings, new app icon, and other UI improvements (#2732) 2025-07-03 11:03:51 -07:00
Totoo 54f9ff116b AIS map improv (#2725)
* AIS map improv

* format code mismatch with vc
2025-06-29 22:47:18 +02:00
Totoo 47f231ad63 Add blue bar to subghzd+weather (#2724) 2025-06-29 19:31:05 +08:00
Netro f90d3fabce Adding simple FSK Rx Processor. Can be used with New Apps. (#2716)
* Work to allow for unique beacon parsing functions.

* Fixing pull.

* Changes.

* Formatting.

* Fix Copyright

* Update firmware/application/apps/ble_rx_app.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update firmware/baseband/proc_btlerx.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* PR suggestions.

* Fix String.

* FSK Rx Improvements. Works for my custom protocol.

* Fix buffer size.

* Refactor

* Formatting.

* Formatting.

* Fixing compiling, and BLE Rx UI/Performance.

* More improvements.

* Fixing stuck state.

* More stuck parsing fix.

* Combining PR changes.

* Improvements from previous PR.

* Fix dbM calculation relative to device RSSI.

* Formatting.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: TJ <tj.baginski@cognosos.com>
2025-06-29 01:02:12 +02:00
RocketGod 4e276cdc71 Battleship (#2720)
* Made the Battleship 2P 2PP game - FSK is wip
* Using POCSAG
2025-06-28 20:02:29 +02:00
plomek 0eb03373b1 Added 3d printed cases for the H4M (#2715) 2025-06-25 19:16:43 +02:00
Netro d5ea0f0369 BLE Rx Improvements (#2710)
* Work to allow for unique beacon parsing functions.
* Fix Copyright
* Update firmware/application/apps/ble_rx_app.cpp
* Update firmware/baseband/proc_btlerx.cpp
* PR suggestions.
* Fix String.
* Refactor
2025-06-25 19:14:04 +02:00
RocketGod 22cc311447 Update app icons for Space Invaders and Dino Game (#2713) 2025-06-25 16:08:48 +02:00
RocketGod 4fbba205ad Made the Blackjack game (#2712)
* Made the Blackjack game
* Format Blackjack main.cpp
* Changed spade to diamond for dark mode visibility
* Format code
2025-06-25 16:07:56 +02:00
RocketGod 7b91103118 Made the Space Invaders game. Argh matey! (#2709)
* Made the Space Invaders game. Argh matey!
* Format code, sigh.
2025-06-21 08:35:52 +02:00
Tommaso Ventafridda c0aa4a1738 ADSB database update (tools, db) (#2701)
* enhance make_airlines_db tool
* enhance make_icao24_db tool
* update airlinescode (.txt, .db), aircraftdatabase/icao24 (.csv, .db)
2025-06-19 17:28:49 +02:00
Tommaso Ventafridda e26f77ee77 Filemanager: go to parent directory keep track of the right selected … (#2702)
* Filemanager: go to parent directory keep track of the right selected item and page number
* review: avoid unnecessary copies in get_extension
2025-06-19 17:24:38 +02:00
plomek f53262783a Improved FPV_ANALOG.txt FREQMAN file (#2700)
* Improved FPV_ANALOG.txt FREQMAN file
Removed unused or super rare analog fpv bands: U, O, H, D
Added 1.2GHz -1.3GHz channels sometimes used for long range analog fpv


* Corrected and updated the labels to be more consistent.

Corrected the labels to be more consistent.

I also somehow messed up the correct channels because 1.3GHz FPV is not fully standardized, but these channels seem to be the most common.
It should be all correct now.

Example transmitters using those channels:
https://greenchip.com.ua/0-0-1615-2.html
https://flymod.net/en/item/walksnail_vtx_9ch
https://pl.aliexpress.com/item/1005006505365351.html
2025-06-19 05:26:02 +00:00
Netro b456c18008 Modified Text Editor to handle long presses. (#2698) 2025-06-19 07:21:54 +02:00
Tommaso Ventafridda fa4b74fd6f Add vendor name in bluetooth rx app (#2696)
* add macaddress db, add vendor name in bluetooth rx app

* show "missing macaddress.db" instead of unknown if db not found

* bluetooth rx list with colors based on mac vendor

* bug fix
2025-06-16 10:57:58 -07:00
RocketGod 18bc2cf11c Made the Dino Game (#2697) 2025-06-15 09:25:10 -07:00
Tommaso Ventafridda 20f28c8331 add dark theme (#2695) 2025-06-13 18:34:20 +02:00
Tommaso Ventafridda ea38a0fe48 Add 1ms hop option to hopper app + 0ms (freeze UI) (#2692) 2025-06-12 11:54:37 +02:00
RocketGod fb2e576b34 Super secret dont look (#2690)
* Add new jammer modes

Overview

This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.

* Super secret

* You gotta get (Get) that (That) dirt off your shoulder
2025-06-10 13:36:26 -07:00
Tommaso Ventafridda 1d79e30d4b Add all jammer modes in hopper app (#2691)
Added all modes that jammer app supports in hopper app.
2025-06-10 06:54:59 -07:00
Totoo be372e12bc Externalize widget (#2688) 2025-06-09 12:52:40 +02:00
Totoo 00853f526a Gfx widget and Radio (#2685)
* widgetize
* gfx and Radio improvement
* format + handle not wfm visual states
* wf or gf
2025-06-07 11:43:07 +02:00
Petro Danylevskyi 37ca7a601c Rename looking glass preset for clarity and consistency (#2686) 2025-06-07 11:27:25 +02:00
RocketGod b50d18eafc Radio app improvements (#2680) 2025-06-05 21:25:09 +02:00
Totoo 1070d951e6 80mhz jammer range (#2682)
Looks great 😎🤘🚀
2025-06-05 07:08:03 -07:00
Totoo a980fe21ac removed some std stuff only used here (#2681) 2025-06-05 11:32:26 +02:00
Alien 9e96715176 Added ability to enter custom tone values in Morse app (#2679)
* Added ability to enter custom tone values in Morse app

Added the ability to type in a custom tone value in the morse TX app (issue#2582)

*Click on the tone field to open a keyboard for entering in a desired value between 100hz - 9999hz.

*Maintains original step value of 20 when scrolling the rotary wheel.

* Update ui_morse.cpp

Replaced std::to_string with to_string_dec_uint

* Moved tone_input_buffer init to in-class
2025-06-05 05:21:44 +00:00
Brumi-2021 fecfe8b8fc Clean_LCD_beat_in_NOAA_Rx_App (#2678) 2025-06-03 21:10:58 +02:00
zix dfdd52c667 trivial apps folder movement (#2677) 2025-06-03 12:13:44 +02:00
Brumi-2021 695e6d19f4 Adding_BPF_selection_to_the_NOAA_APT_signal (#2675)
* Adding_BPF_selection_to_the_NOAA_APT_signal
* comments, spell mistake .
2025-06-02 14:40:13 +02:00
Totoo 6e9ecf8e7b jammer fix (#2676)
* jammer fix
2025-06-02 19:58:53 +08:00
RocketGod fd158e873a Jammer improvements (#2674)
* Add new jammer modes
Overview:
This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.

* Fixed and made brutal.

This PR introduces user-focused improvements to the Jammer App in the HackRF PortaPack Mayhem Firmware, enhancing usability and flexibility. The changes address specific user requirements for a more intuitive default configuration, continuous waveform support, and dynamic setting adjustments during transmission.
2025-05-31 22:16:57 +02:00
Totoo f90bd44617 Detector RX ext app (#2673) 2025-05-28 19:19:47 +08:00
Totoo 2602c417be ui new coord system examples and macros (#2672) 2025-05-27 17:37:41 +08:00
Totoo aa1264cf91 getres cmd (#2671) 2025-05-27 15:18:33 +08:00
Brumi-2021 c79abccffd wfm_filters_GUI_name_std (#2668) 2025-05-25 20:04:08 +02:00
Brumi-2021 d3c9dc1f10 Storing_selected_NOAA_filter_in_settings_file (#2665)
* Storing_selected_NOAA_filter_in_settings_file

* format_issues
2025-05-24 18:18:24 +02:00
Totoo 3c8335c942 Audio to right (#2664)
* r.align
2025-05-23 12:27:57 +02:00
Totoo ac31f4781c screen max width fixes (#2663)
* max width fixes

* format
2025-05-23 08:42:19 +02:00
Totoo c80cae3638 fix png part 2025-05-23 00:23:35 +08:00
Totoo a1d7cf2b86 Prepare for display orientation part 1 (#2661) 2025-05-22 23:24:53 +08:00
zix 6f6d863a14 refactor the serial log logic of BLE Rx (#2660) 2025-05-21 07:07:26 -07:00
RocketGod 43a1bc0445 Jammer app add modes (#2659)
* Add new jammer modes

Overview

This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.

* Add new jammer modes

Overview

This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.
2025-05-19 19:16:28 +02:00
OpenSourceSDRLab 0d02f431c7 Upload the PCB file of PortaPack H4 and update the schematic file (#2657)
* Create README.txt

* Update README.txt

* Add files via upload

The gerber files of the portapack h4.

* delete

* Upload the gerber files for H4

* Create README.txt

This is the V1.0 version PCB file of PortaPack H4.

* Upload the pcb file for H4

* Update README.txt

* Update LCD_TF_Schematic.pdf
2025-05-19 23:40:02 +12:00
465 changed files with 38539 additions and 4836 deletions
+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.0.2
v2.3.1
+1 -1
View File
@@ -1 +1 @@
v2.1.0
v2.3.2
+1
View File
@@ -62,6 +62,7 @@ cmake-build-debug/
.vscode
.idea
*.swp
.claude
# VSCodium extensions
.history
-7
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
@@ -317,7 +311,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
+3
View File
@@ -188,6 +188,7 @@ void copy_to_radio_model(const AppSettings& settings) {
settings.am_config_index,
settings.nbfm_config_index,
settings.wfm_config_index,
settings.wfmam_config_index,
settings.squelch);
}
@@ -218,6 +219,7 @@ void copy_from_radio_model(AppSettings& settings) {
settings.am_config_index = receiver_model.am_configuration();
settings.nbfm_config_index = receiver_model.nbfm_configuration();
settings.wfm_config_index = receiver_model.wfm_configuration();
settings.wfmam_config_index = receiver_model.wfmam_configuration();
}
settings.step = receiver_model.frequency_step();
@@ -271,6 +273,7 @@ SettingsManager::SettingsManager(
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
bindings_.emplace_back("wfmam_config_index"sv, &settings_.wfmam_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
+1
View File
@@ -139,6 +139,7 @@ struct AppSettings {
uint8_t am_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t wfmam_config_index = 0;
uint8_t squelch = 80;
uint8_t volume;
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
+52 -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);
}
@@ -301,7 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
ais::format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::NONE,
GeoPos::spd_unit::KNOTS,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
@@ -324,7 +324,31 @@ 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);
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) {
if (geomap_view && send_updates) {
GeoMarker marker{};
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
marker.angle = entry.last_position.true_heading;
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
auto markerStored = geomap_view->store_marker(marker);
return markerStored == MARKER_STORED;
}
return false;
}
void AISRecentEntryDetailView::update_map_markers(AISRecentEntries& entries) {
if (geomap_view && send_updates) {
geomap_view->clear_markers();
for (const auto& entry : entries) {
// if (entry.last_position.latitude.is_valid() && entry.last_position.longitude.is_valid()) {
add_map_marker(entry);
// }
}
update_position(); // to update view
}
}
void AISRecentEntryDetailView::focus() {
@@ -415,14 +439,30 @@ AISAppView::AISAppView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
}
AISAppView::~AISAppView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
void AISAppView::on_tick_second() {
++timer_seconds;
if (timer_seconds % 10 == 0) {
if (recent_entry_detail_view.hidden()) return;
if (got_new_packet) {
got_new_packet = false;
recent_entry_detail_view.update_map_markers(recent);
}
}
}
void AISAppView::focus() {
options_channel.focus();
}
@@ -438,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();
@@ -461,6 +501,7 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.hidden(false);
recent_entry_detail_view.set_entry(entry);
recent_entry_detail_view.focus();
recent_entry_detail_view.update_map_markers(recent);
}
} /* namespace ui */
+21 -13
View File
@@ -125,18 +125,22 @@ class AISRecentEntryDetailView : public View {
void update_position();
void focus() override;
void paint(Painter&) override;
bool add_map_marker(const AISRecentEntry& entry);
void update_map_markers(AISRecentEntries& entries);
AISRecentEntryDetailView(const AISRecentEntryDetailView& Entry);
AISRecentEntryDetailView& operator=(const AISRecentEntryDetailView& Entry);
GeoMapView* get_geomap_view() { return geomap_view; }
private:
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};
@@ -155,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;
@@ -175,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_};
@@ -185,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},
@@ -197,24 +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{
{28 * 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,
@@ -229,6 +236,7 @@ class AISAppView : public View {
void on_packet(const ais::Packet& packet);
void on_show_list();
void on_show_detail(const AISRecentEntry& entry);
void on_tick_second();
};
} /* namespace ui */
+33 -33
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,12 +138,12 @@ 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},
16, // Max option char length "80khz (NOAA Apt)" example.
{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{
{28 * 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, 30 * 8, 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{
{28 * 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, 30 * 8, 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);
}
}
}
+21 -20
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,14 +164,14 @@ 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},
"-"};
Console console{
{0, 4 * 16, 240, 240}};
{0, 4 * 16, ui::screen_width, ui::screen_height - 80}};
std::string str_log{""};
bool logging{false};
+443 -194
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -33,6 +34,9 @@
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_textentry.hpp"
#include "usb_serial_asyncmsg.hpp"
#include <string_view>
using namespace portapack;
using namespace modems;
@@ -66,6 +70,50 @@ uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
return result;
}
MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
static bool db_checked = false;
static bool db_exists = false;
if (!db_checked) {
database db;
database::MacAddressDBRecord dummy_record;
int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
db_exists = (test_result != DATABASE_NOT_FOUND);
db_checked = true;
}
if (!db_exists) {
vendor_name = "macaddress.db not found";
return MAC_DB_NOT_FOUND;
}
database db;
database::MacAddressDBRecord record;
// Convert MAC address to hex string
std::string mac_hex = "";
for (int i = 0; i < 3; i++) {
// Only need first 3 bytes for OUI
mac_hex += to_string_hex(mac_address[i], 2);
}
int result = db.retrieve_macaddress_record(&record, mac_hex);
if (result == DATABASE_RECORD_FOUND) {
vendor_name = std::string(record.vendor_name);
return MAC_VENDOR_FOUND;
} else {
vendor_name = "Unknown";
return MAC_VENDOR_NOT_FOUND;
}
}
std::string lookup_mac_vendor(const uint8_t* mac_address) {
std::string vendor_name;
lookup_mac_vendor_status(mac_address, vendor_name);
return vendor_name;
}
void reverse_byte_array(uint8_t* arr, int length) {
int start = 0;
int end = length - 1;
@@ -133,24 +181,26 @@ 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;
if (!entry.informationString.empty()) {
hitsStr = entry.informationString;
} else {
hitsStr = to_string_dec_int(entry.numHits);
}
// Pushing single digit values down right justified.
std::string hitsStr = to_string_dec_int(entry.numHits);
int hitsDigits = hitsStr.length();
uint8_t hits_spacing = 8 - hitsDigits;
@@ -164,7 +214,10 @@ void RecentEntriesTable<BleRecentEntries>::draw(
line += pad_string_with_spaces(db_spacing) + dbStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
painter.draw_string(target_rect.location(), row_style, line);
}
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
@@ -177,17 +230,20 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
&text_mac_address,
&label_pdu_type,
&text_pdu_type,
&label_vendor,
&text_vendor,
&labels});
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
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);
});
@@ -195,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(
@@ -369,10 +425,13 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
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));
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));
@@ -380,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;
}
@@ -431,39 +490,82 @@ 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,
&recent_entries_view});
async_tx_states_when_entered = portapack::async_tx_enabled;
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push<BleRecentEntryDetailView>(entry);
};
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
usb_serial_thread = std::make_unique<UsbSerialThread>();
} else {
usb_serial_thread.reset();
}
};
check_serial_log.set_value(serial_logging);
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) {
portapack::async_tx_enabled = true;
} else {
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);
// ------------------------------------------------------------------------------
// Handle Buttons
// ------------------------------------------------------------------------------
filterBuffer = filter;
button_filter.on_select = [this](Button&) {
@@ -477,17 +579,6 @@ BLERxView::BLERxView(NavigationView& nav)
});
};
logger = std::make_unique<BLELogger>();
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
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(
@@ -502,23 +593,25 @@ BLERxView::BLERxView(NavigationView& nav)
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
button_switch.on_select = [&nav](Button&) {
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;
@@ -542,23 +635,16 @@ BLERxView::BLERxView(NavigationView& nav)
};
options_filter.on_change = [this](size_t index, int32_t v) {
(void)v;
filter_index = (uint8_t)index;
handle_filter_options(v);
recent.clear();
recent_entries_view.set_dirty();
};
options_channel.set_selected_index(channel_index, true);
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);
@@ -716,78 +802,87 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
}
void BLERxView::on_data(BlePacketData* packet) {
if (!logging) {
str_log = "";
}
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;
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
}
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.
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
// Add entries if they meet the criteria.
// auto value = filter;
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
// });
handle_filter_options(options_filter.selected_index());
uint64_t key = (uniqueKeyEncoded & 0xFFFFFFFFFFFF);
handle_entries_sort(options_sort.selected_index());
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);
} else {
recent.emplace_front(key);
truncate_entries(recent);
}
if (updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type)) {
if (!searchList.empty()) {
auto it = searchList.begin();
while (it != searchList.end()) {
std::string searchStr = (std::string)*it;
if (recent.front().dataString.find(searchStr) != std::string::npos) {
searchList.erase(it);
found_count++;
break;
}
it++;
}
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) {
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;
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
}
}
// Log at End of Packet.
if (logger && logging) {
logger->log_raw_data(str_console + "\r\n");
logger->log_raw_data(str_console);
}
if (serial_logging) {
usb_serial_thread->serial_str = str_console + "\r\n";
usb_serial_thread->str_ready = true;
}
str_console = "";
if (!searchList.empty()) {
auto it = searchList.begin();
while (it != searchList.end()) {
std::string searchStr = (std::string)*it;
if (entry.dataString.find(searchStr) != std::string::npos) {
searchList.erase(it);
found_count++;
break;
}
it++;
}
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
UsbSerialAsyncmsg::asyncmsg(str_console); // new line handled there, no need here.
}
}
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();
}
}
@@ -839,13 +934,10 @@ void BLERxView::on_timer() {
timer_count = 0;
if (auto_channel) {
int min = 37;
int max = 39;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
baseband::set_btlerx(channel_number);
int randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
baseband::set_btlerx(randomChannel);
channel_number = (channel_number < 39) ? channel_number + 1 : 37;
}
}
if (ble_rx_error != BLE_RX_NO_ERROR) {
@@ -866,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(
@@ -884,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;
}
@@ -891,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;
}
}
@@ -916,76 +1022,219 @@ void BLERxView::set_parent_rect(const Rect new_parent_rect) {
}
BLERxView::~BLERxView() {
portapack::async_tx_enabled = async_tx_states_when_entered;
receiver_model.disable();
baseband::shutdown();
}
void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
std::string data_string;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB;
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.numHits++;
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
// 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();
bool success = false;
// 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) && entry.nameString.empty()) {
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
if (uniqueParsing) {
success = parse_tracking_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
}
uint8_t currentByte = 0;
uint8_t length = 0;
uint8_t type = 0;
std::string decoded_data;
for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
length = advertiseData->Data[currentByte++];
type = advertiseData->Data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; i < length - 1; i++) {
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
if (type == 0x08 || type == 0x09) {
decoded_data += (char)advertiseData->Data[currentByte];
}
currentByte++;
}
if (!decoded_data.empty()) {
entry.nameString = std::move(decoded_data);
break;
}
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;
}
}
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) {
uint8_t currentByte, currentLength, currentType = 0;
std::string tempName = "";
for (currentByte = 0; currentByte < length;) {
currentLength = data[currentByte++];
currentType = data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; ((i < currentLength - 1) && (currentByte < length)); i++) {
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
if (currentType == 0x08 || currentType == 0x09) {
tempName += (char)data[currentByte];
}
currentByte++;
}
if (!tempName.empty()) {
nameString = tempName;
break;
}
}
informationString = "";
if (!informationString.empty()) {
// Option to change title of Hits Column.
// Setting to default for now.
columns.set(1, "Hits", 7);
}
return true;
}
} /* namespace ui */
+102 -56
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -33,6 +34,7 @@
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "database.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "usb_serial_thread.hpp"
@@ -69,24 +71,35 @@ typedef enum {
RESERVED5 = 12,
RESERVED6 = 13,
RESERVED7 = 14,
RESERVED8 = 15
RESERVED8 = 15,
UNKNOWN = 16
} ADV_PDU_TYPE;
typedef enum {
MAC_VENDOR_UNKNOWN = 0,
MAC_VENDOR_FOUND = 1,
MAC_VENDOR_NOT_FOUND = 2,
MAC_DB_NOT_FOUND = 3
} MAC_VENDOR_STATUS;
struct BleRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0xffffffff;
static constexpr Key invalid_key = 0xFFFFFFFFFFFFF;
uint64_t macAddress;
uint64_t uniqueKey;
int dbValue;
BlePacketData packetData;
std::string timestamp;
std::string dataString;
std::string nameString;
std::string informationString;
bool include_name;
uint16_t numHits;
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
MAC_VENDOR_STATUS vendor_status;
std::string vendor_name;
bool entryFound;
BleRecentEntry()
@@ -94,22 +107,25 @@ struct BleRecentEntry {
}
BleRecentEntry(
const uint64_t macAddress)
: macAddress{macAddress},
const uint64_t uniqueKey)
: uniqueKey{uniqueKey},
dbValue{},
packetData{},
timestamp{},
dataString{},
nameString{},
informationString{},
include_name{},
numHits{},
pduType{},
channelNumber{},
vendor_status{MAC_VENDOR_UNKNOWN},
vendor_name{},
entryFound{} {
}
Key key() const {
return macAddress;
return uniqueKey;
}
};
@@ -126,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{};
@@ -139,35 +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{
{{UI_POS_X(0), UI_POS_Y(2)}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
Text text_vendor{
{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};
@@ -186,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;
@@ -198,15 +221,19 @@ class BLERxView : public View {
bool saveFile(const std::filesystem::path& path);
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
void on_data(BlePacketData* packetData);
void log_ble_packet(BlePacketData* packet);
void on_filter_change(std::string value);
void on_file_changed(const std::filesystem::path& new_file_path);
void file_error();
void on_timer();
void handle_entries_sort(uint8_t index);
void handle_filter_options(uint8_t index);
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
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_;
RxRadioState radio_state_{
2402000000 /* frequency */,
4000000 /* bandwidth */,
@@ -216,9 +243,12 @@ class BLERxView : public View {
uint8_t channel_index{0};
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};
bool async_tx_states_when_entered{false};
bool name_enable{true};
app_settings::SettingsManager settings_{
@@ -233,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 = "";
@@ -242,7 +274,7 @@ class BLERxView : public View {
bool auto_channel = false;
int16_t timer_count{0};
int16_t timer_period{6}; // 100ms
int16_t timer_period{1}; // 25ms
std::string filterBuffer{};
std::string listFileBuffer{};
@@ -258,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},
@@ -270,45 +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},
4,
{18 * 8 + 2, UI_POS_Y(1)},
7,
{{"Data", 0},
{"MAC", 1}}};
{"MAC", 1},
{"Name", 2},
{"Info", 3},
{"Vendor", 4},
{"Channel", 5}}};
Checkbox check_log{
{10 * 8, 4 * 8 + 2},
@@ -317,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, 240, 240}};
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, 320 - (16 + 32), 7 * 8, 32},
{2 * 8, screen_height - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
{11 * 8, 320 - (16 + 32), 11 * 8, 32},
{UI_POS_X_CENTER(11), screen_height - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
{240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
{UI_POS_X_RIGHT(6), screen_height - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
@@ -360,10 +406,10 @@ class BLERxView : public View {
BleRecentEntries recent{};
BleRecentEntries tempList{};
const RecentEntriesColumns columns{{
{"Mac Address", 17},
RecentEntriesColumns columns{{
{"Name", 0},
{"Hits", 7},
{"dB", 4},
{"dBm", 4},
}};
BleRecentEntriesView recent_entries_view{columns, recent};
+137 -83
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,73 +281,77 @@ 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);
}
}
}
}
BLETxView::BLETxView(NavigationView& nav)
: nav_{nav} {
add_children({&button_open,
add_children({&dataEditView,
&button_open,
&text_filename,
&progressbar,
&check_rand_mac,
@@ -330,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,
@@ -340,7 +371,6 @@ BLETxView::BLETxView(NavigationView& nav)
&label_mac_address,
&text_mac_address,
&label_data_packet,
&dataEditView,
&button_clear_marked,
&button_save_packet,
&button_switch});
@@ -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) {
@@ -430,6 +455,23 @@ BLETxView::BLETxView(NavigationView& nav)
}
};
dataEditView.on_change = [this](uint8_t value) {
// Reject setting newline at index 29.
if (cursor_pos.col != 29) {
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
update_current_packet(packets[current_packet], current_packet);
}
};
dataEditView.on_cursor_moved = [this]() {
// Save last selected cursor.
cursor_pos.line = dataEditView.line();
cursor_pos.col = dataEditView.col();
};
button_clear_marked.on_select = [this](Button&) {
marked_counter = 0;
markedBytes.clear();
@@ -465,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 {
@@ -493,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();
}
}
@@ -523,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;
@@ -531,10 +585,9 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
for (const std::string& str : strings) {
dataFile.write(str.c_str(), str.size());
dataFile.write("\n", 1);
;
}
dataFile.~File();
dataFile.close();
auto result = FileWrapper::open(dataTempFilePath);
@@ -546,6 +599,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
dataEditView.set_font_zoom(true);
dataEditView.set_file(*dataFileWrapper);
dataEditView.redraw(true, true);
dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
}
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
+33 -31
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{
{28 * 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,45 +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}};
Console console{
{0, 9 * 18, 240, 240}};
{{UI_POS_X(0), 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
TextViewer dataEditView{
{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{""};
@@ -323,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, 30 * 8, 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{
{28 * 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 ****",
" ",
@@ -34,6 +34,7 @@ constexpr std::string_view mayhem_information_list[] = {
"RedFox-Fr,nemanjan00,",
"MichalLeonBorsuk,",
"MatiasFernandez,Giorgiofox",
"TommasoVentafridda",
" ",
"#Havoc:",
"jboone,furrtek,eried,argilo,",
@@ -6,6 +6,10 @@
#include <cstdint>
/*
TODO: Now it is dyn width. There should be an algorithm to fill the menu based on it's size.
*/
namespace ui {
class AboutView : public View {
public:
@@ -22,11 +26,11 @@ class AboutView : public View {
uint16_t frame_sync_count{0};
void on_frame_sync();
MenuView menu_view{
{0, 0, 240, 264},
{0, 0, screen_width, screen_height - 56},
true};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
{screen_width / 3, screen_height - 50, screen_width / 3, 24},
"OK",
};
+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, 240, 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, 30 * 8, 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, 30 * 8, 16},
{18 * 8, 8 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_engine_type{
{0 * 8, 10 * 16, 30 * 8, 16},
{UI_POS_X(0), 10 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_owner{
{0 * 8, 12 * 16, 30 * 8, 16},
{UI_POS_X(0), 12 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_operator{
{0 * 8, 14 * 16, 30 * 8, 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, 30 * 8, 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, 30 * 8, 16},
{UI_POS_X(0), 6 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_info2{
{0 * 8, 7 * 16, 30 * 8, 16},
{UI_POS_X(0), 7 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_frame_pos_even{
{0 * 8, 14 * 16, 30 * 8, 16},
{UI_POS_X(0), 14 * 16, screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_frame_pos_odd{
{0 * 8, 16 * 16, 30 * 8, 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{
{28 * 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, 240, 280});
details_view.set_parent_rect({0, 0, 240, 280});
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, 240, 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{
{28 * 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, 30 * 8, 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, 240, 240}};
{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, 240, 280};
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};
+6 -6
View File
@@ -61,9 +61,9 @@ class APRSTXView : public View {
void on_tx_progress(const uint32_t progress, const bool done);
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), 1 * 16}, "Source: SSID:", Theme::getInstance()->fg_light->foreground}, // 6 alphanum + SSID
{{UI_POS_X(0), 2 * 16}, " Dest.: SSID:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 4 * 16}, "Info field:", Theme::getInstance()->fg_light->foreground},
};
SymField sym_source{
@@ -91,14 +91,14 @@ class APRSTXView : public View {
' '};
Text text_payload{
{0 * 8, 5 * 16, 30 * 8, 16},
{UI_POS_X(0), 5 * 16, screen_width, 16},
"-"};
Button button_set{
{0 * 8, 6 * 16, 80, 32},
{UI_POS_X(0), 6 * 16, 80, 32},
"Set"};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
5000,
0 // disable setting bandwith, since APRS used fixed 10k bandwidth
};
+2 -2
View File
@@ -90,7 +90,7 @@ class BattinfoView : public View {
"-"};
Text text_warn{
{1 * 8, 8 * 16, 30 * 8, 2 * 16},
{1 * 8, 8 * 16, screen_width, 2 * 16},
""}; */
Button button_mode{
@@ -98,7 +98,7 @@ class BattinfoView : public View {
"Volt"};
Button button_exit{
{72, 17 * 16, 96, 32},
{UI_POS_X_CENTER(12), 17 * 16, UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"Back"};
static msg_t static_fn(void* arg);
Thread* thread{nullptr};
@@ -42,7 +42,7 @@ class BMPFileViewer : public View {
private:
NavigationView& nav_;
std::filesystem::path path_{};
BMPViewer bmp{{0, 0, 240, 320}};
BMPViewer bmp{{0, 0, screen_width, screen_height}};
};
} // namespace ui
+8 -8
View File
@@ -63,26 +63,26 @@ 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{
{240 - 12 * 8, 1 * 16, 96, 24},
{screen_width - 12 * 8, UI_POS_Y(1), 96, 24},
"Modem setup"};
Console console{
{0, 4 * 16, 240, 240}};
{0, 4 * 16, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
+3 -3
View File
@@ -202,10 +202,10 @@ RegistersView::RegistersView(
};
button_done.on_select = [&nav](Button&) { nav.pop(); };
registers_widget.set_parent_rect({0, 48, 240, 192});
registers_widget.set_parent_rect({0, 48, screen_width, 192});
registers_widget.set_page(0);
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
text_title.set_parent_rect({(screen_width - static_cast<int>(title.size()) * 8) / 2, 16,
static_cast<int>(title.size()) * 8, 16});
text_title.set(title);
@@ -482,7 +482,7 @@ DebugPmemView::DebugPmemView(NavigationView& nav)
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({&registers_widget, &text_checksum, &text_checksum2, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
registers_widget.set_parent_rect({0, 32, screen_width, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
+14 -14
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, 240, 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},
@@ -332,7 +332,7 @@ class DebugPmemView : public View {
Text text_checksum2{{16, 248, 208, 16}};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
{screen_width / 3, 270, screen_width / 3, 24},
"OK",
};
@@ -364,7 +364,7 @@ public:
private:
Console console {
{ 8, 16, 224, 240 }
{ 8, 16, 224, screen_height-80 }
};
Button button_exit {
@@ -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);
+4 -4
View File
@@ -124,7 +124,7 @@ class EncodersConfigView : public View {
""};
Waveform waveform{
{0, 17 * 8, 240, 32},
{0, 17 * 8, screen_width, 32},
waveform_buffer,
0,
0,
@@ -201,7 +201,7 @@ class EncodersView : public View {
void start_tx(const bool scan);
void on_tx_progress(const uint32_t progress, const bool done);
Rect view_rect = {0, 4 * 8, 240, 168};
Rect view_rect = {0, 4 * 8, screen_width, 168};
EncodersConfigView view_config{nav_, view_rect};
EncodersScanView view_scan{nav_, view_rect};
@@ -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};
@@ -84,7 +84,7 @@ class ExternalModuleView : public View {
Text text_app5_name{{24, 160, 200, 16}};
Button dummy{
{240, 0, 0, 0},
{screen_width, 0, 0, 0},
""};
SignalToken signal_token_tick_second{};
+69 -94
View File
@@ -228,6 +228,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
// Collect all entries first
std::list<fileman_entry> all_entries;
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
// Hide files starting with '.' (hidden / tmp).
if (!show_hidden_files && is_hidden_file(entry.path()))
@@ -235,36 +237,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path().string(), 0, true});
insert_sorted(all_entries, {entry.path().string(), 0, true});
}
}
// paginating
auto list_size = entry_list.size();
nb_pages = 1 + (list_size / items_per_page);
size_t start = pagination * items_per_page;
size_t stop = start + items_per_page;
if (list_size > start) {
if (list_size < stop)
stop = list_size;
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
// Calculate pagination
nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
if (nb_pages == 0) nb_pages = 1;
size_t start_idx = pagination * items_per_page;
size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
// Add "parent" directory if not at the root and on first page
if (!dir_path.empty() && pagination == 0) {
entry_list.push_back({parent_dir_path.string(), 0, true});
}
// Add "parent" directory if not at the root.
if (!dir_path.empty() && pagination == 0)
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
// add next page
if (list_size > start + items_per_page) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
// add prev page
// Add prev page navigation if not on first page
if (pagination > 0) {
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
}
// Add entries for current page
auto it = all_entries.begin();
std::advance(it, start_idx);
for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
entry_list.push_back(*it);
}
// Add next page navigation if not on last page
if (end_idx < all_entries.size()) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
}
@@ -288,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});
@@ -320,30 +327,49 @@ void FileManBaseView::focus() {
} else {
menu_view.focus();
}
// Set menu to the correct page and select the correct item
menu_view.set_highlighted(prev_highlight % items_per_page);
}
// Push directory - store the global index (page * items_per_page + local_index)
void FileManBaseView::push_dir(const fs::path& path) {
if (path == parent_dir_path) {
pop_dir();
} else {
// Save global index (combines page number and item position)
saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
current_path /= path;
saved_index_stack.push_back(menu_view.highlighted_index());
menu_view.set_highlighted(0);
reload_current(true);
reload_current(true); // Reset pagination when entering new directory
}
}
void FileManBaseView::pop_dir() {
if (saved_index_stack.empty())
if (current_path.empty()) {
return;
}
// Move to parent directory
current_path = current_path.parent_path();
reload_current(true);
menu_view.set_highlighted(saved_index_stack.back());
saved_index_stack.pop_back();
// Restore the previous global index if available
if (!saved_index_stack.empty()) {
uint32_t global_index = saved_index_stack.back();
saved_index_stack.pop_back();
// Calculate pagination from global index
pagination = global_index / items_per_page;
// Calculate local index within the page
prev_highlight = global_index % items_per_page;
restoring_navigation = true;
}
reload_current(false); // Important: don't reset pagination
}
std::string get_extension(std::string t) {
std::string FileManBaseView::get_extension(const std::string& t) const {
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return {};
@@ -372,11 +398,13 @@ void FileManBaseView::refresh_list() {
if (on_refresh_widgets)
on_refresh_widgets(false);
prev_highlight = menu_view.highlighted_index();
if (!restoring_navigation) {
prev_highlight = menu_view.highlighted_index();
}
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{};
@@ -387,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) {
@@ -400,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) {
@@ -411,10 +439,14 @@ void FileManBaseView::refresh_list() {
}
menu_view.set_highlighted(prev_highlight);
restoring_navigation = false;
}
void FileManBaseView::reload_current(bool reset_pagination) {
if (reset_pagination) pagination = 0;
// Only reset pagination if explicitly requested
if (reset_pagination) {
pagination = 0;
}
load_directory_contents(current_path);
refresh_list();
}
@@ -455,7 +487,7 @@ FileLoadView::FileLoadView(
add_children({&menu_view});
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 29 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(4.5)});
refresh_list();
@@ -489,63 +521,6 @@ void FileLoadView::refresh_widgets(const bool) {
set_dirty();
}
/* FileSaveView **************************************************************/
/*
FileSaveView::FileSaveView(
NavigationView& nav,
const fs::path& path,
const fs::path& file
) : nav_{ nav },
path_{ path },
file_{ file }
{
add_children({
&text_path,
&button_edit_path,
&text_name,
&button_edit_name,
&button_save,
&button_cancel,
});
button_edit_path.on_select = [this](Button&) {
buffer_ = path_.string();
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string&) {
path_ = buffer_;
refresh_widgets();
});
};
button_edit_name.on_select = [this](Button&) {
buffer_ = file_.string();
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string&) {
file_ = buffer_;
refresh_widgets();
});
};
button_save.on_select = [this](Button&) {
if (on_save)
on_save(path_ / file_);
else
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
nav_.pop();
};
refresh_widgets();
}
void FileSaveView::refresh_widgets() {
text_path.set(truncate(path_, 30));
text_name.set(truncate(file_, 30));
set_dirty();
}
*/
/* FileManagerView ***********************************************************/
void FileManagerView::refresh_widgets(const bool v) {
+21 -19
View File
@@ -64,7 +64,8 @@ class FileManBaseView : public View {
uint32_t prev_highlight = 0;
uint8_t pagination = 0;
uint8_t nb_pages = 1;
static constexpr size_t max_filename_length = 20;
bool restoring_navigation = false;
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;
@@ -96,6 +97,7 @@ class FileManBaseView : public View {
void load_directory_contents(const std::filesystem::path& dir_path);
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
std::string get_extension(const std::string& t) const;
void copy_waterfall(std::filesystem::path path);
NavigationView& nav_;
@@ -120,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, 240, 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"};
};
@@ -172,7 +174,7 @@ private:
};
Text text_path {
{ 0 * 8, 2 * 16, 30 * 8, 16 },
{ 0 * 8, 2 * 16, screen_width, 16 },
"",
};
@@ -182,7 +184,7 @@ private:
};
Text text_name {
{ 0 * 8, 7 * 16, 30 * 8, 16 },
{ 0 * 8, 7 * 16, screen_width, 16 },
"",
};
@@ -228,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,
@@ -295,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};
@@ -80,7 +80,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
ensure_directory(apps_dir);
ensure_directory(firmware_dir);
@@ -130,6 +130,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(
+10 -1
View File
@@ -49,6 +49,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 +61,21 @@ class FlashUtilityView : public View {
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, 240, 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, 30 * 8, 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"};
};
+2 -1
View File
@@ -34,6 +34,7 @@ namespace ui {
IQTrimView::IQTrimView(NavigationView& nav)
: nav_{nav} {
power_buckets_.resize(screen_width);
add_children({
&labels,
&field_path,
@@ -170,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()};
+12 -12
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() {
@@ -96,26 +96,26 @@ class IQTrimView : public View {
std::filesystem::path path_{};
Optional<iq::CaptureInfo> info_{};
std::array<iq::PowerBuckets::Bucket, screen_width> power_buckets_{};
std::vector<iq::PowerBuckets::Bucket> power_buckets_{};
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, 30 * 8, 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"};
};
@@ -94,10 +94,10 @@ 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 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_W(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
// 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
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (screen_width / 2);
} else
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / screen_width;
return freq_at_pos;
}
@@ -122,7 +122,7 @@ void GlassView::reset_live_view() {
// Clear screen in peak mode.
if (live_frequency_view == 2)
display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0});
display.fill_rectangle({{0, 108 + 16}, {screen_width, screen_height - (108 + 16)}}, {0, 0, 0});
}
void GlassView::add_spectrum_pixel(uint8_t power) {
@@ -130,7 +130,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
spectrum_data[pixel_index] = (live_frequency_integrate * spectrum_data[pixel_index] + power) / (live_frequency_integrate + 1); // smoothing
pixel_index++;
if (pixel_index == SCREEN_W) // got an entire waterfall line
if (pixel_index == screen_width) // got an entire waterfall line
{
if (live_frequency_view > 0) {
constexpr int rssi_sample_range = SPEC_NB_BINS;
@@ -140,22 +140,22 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
constexpr int raw_delta = raw_max - raw_min;
const range_t<int> y_max_range{0, 320 - (108 + 16)};
const range_t<int> y_max_range{0, screen_height - (108 + 16)};
// drawing and keeping track of max freq
for (uint16_t xpos = 0; xpos < SCREEN_W; xpos++) {
for (uint16_t xpos = 0; xpos < screen_width; xpos++) {
// save max powerwull freq
if (spectrum_data[xpos] > max_freq_power) {
max_freq_power = spectrum_data[xpos];
max_freq_hold = get_freq_from_bin_pos(xpos);
}
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (screen_height - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
// clear if not in peak view
if (live_frequency_view != 2) {
display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0});
display.fill_rectangle({{xpos, 108 + 16}, {1, screen_height - point}}, {0, 0, 0});
}
display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
}
if (last_max_freq != max_freq_hold) {
last_max_freq = max_freq_hold;
@@ -163,7 +163,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
}
plot_marker(marker_pixel_index);
} else {
display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier
display.draw_pixels({{0, display.scroll(1)}, {screen_width, 1}}, spectrum_row); // new line at top, one less var, speedier
}
pixel_index = 0; // Start New cascade line
}
@@ -199,7 +199,7 @@ bool GlassView::process_bins(uint8_t* powerlevel) {
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_W (240) of those bins
// we actually need screen_width (240) of those bins
for (uint8_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (max_power > range_max_power)
@@ -238,7 +238,7 @@ void GlassView::on_hide() {
}
void GlassView::on_show() {
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
baseband::spectrum_streaming_start();
}
@@ -253,11 +253,11 @@ void GlassView::on_range_changed() {
// if the view is done in one pass, show it like in analog_audio_app
mode = LOOKING_GLASS_SINGLEPASS;
offset = 2;
bin_length = SCREEN_W;
bin_length = screen_width;
ignore_dc = 0;
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_range;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
each_bin_size = looking_glass_bandwidth / screen_width;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else {
@@ -269,7 +269,7 @@ void GlassView::on_range_changed() {
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
ignore_dc = 4;
bin_length = SCREEN_W;
bin_length = screen_width;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 2;
bin_length = 80;
@@ -279,7 +279,7 @@ void GlassView::on_range_changed() {
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
search_span = looking_glass_range / MHZ_DIV;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
marker_pixel_step = looking_glass_range / screen_width; // Each pixel value in Hz
pixel_index = 0;
max_power = 0;
@@ -304,10 +304,10 @@ void GlassView::plot_marker(uint8_t pos) {
{
shift_y = 16;
}
portapack::display.fill_rectangle({0, 100 + shift_y, SCREEN_W, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
portapack::display.fill_rectangle({0, 100 + shift_y, screen_width, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
}
void GlassView::update_min(int32_t v) {
@@ -358,7 +358,8 @@ GlassView::GlassView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
spectrum_row.resize(screen_width);
spectrum_data.resize(screen_width);
if (!gradient.load_file(default_gradient_file)) {
gradient.set_default();
}
@@ -437,11 +438,11 @@ GlassView::GlassView(
freq_stats.hidden(true);
button_jump.hidden(true);
button_rst.hidden(true);
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates.
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
break;
case 1: // LEVEL
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -451,7 +452,7 @@ GlassView::GlassView(
case 2: // PEAK
default:
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -491,9 +492,9 @@ GlassView::GlassView(
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
if ((marker_pixel_index + delta) < 0)
marker_pixel_index = marker_pixel_index + delta + SCREEN_W;
else if ((marker_pixel_index + delta) > SCREEN_W)
marker_pixel_index = marker_pixel_index + delta - SCREEN_W;
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();
@@ -531,7 +532,7 @@ GlassView::GlassView(
};
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
display.scroll_set_area(109, 319);
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
// trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute,
@@ -539,7 +540,7 @@ GlassView::GlassView(
// at which time it pushes the buffer up with channel_spectrum.feed
baseband::set_spectrum(looking_glass_bandwidth, trigger);
marker_pixel_index = SCREEN_W / 2;
marker_pixel_index = screen_width / 2;
on_range_changed(); // Force a UI update.
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
@@ -51,9 +51,6 @@ namespace ui {
#define LOOKING_GLASS_SINGLEPASS 2
// one spectrum line number of bins
#define SPEC_NB_BINS 256
// screen dimensions
#define SCREEN_W 240
#define SCREEN_H 320
class GlassView : public View {
public:
@@ -155,8 +152,8 @@ class GlassView : public View {
uint8_t min_color_power{0}; // Filter cutoff level.
uint32_t pixel_index{0};
std::array<Color, SCREEN_W> spectrum_row{};
std::array<uint8_t, SCREEN_W> spectrum_data{};
std::vector<Color> spectrum_row{};
std::vector<uint8_t> spectrum_data{};
ChannelSpectrumFIFO* fifo{};
int32_t steps = 1;
@@ -168,12 +165,12 @@ class GlassView : public View {
rf::Frequency max_freq_hold = 0;
rf::Frequency last_max_freq = 0;
int16_t max_freq_power = -1000;
uint8_t bin_length = SCREEN_W;
uint8_t bin_length = screen_width;
uint8_t offset = 0;
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},
@@ -181,24 +178,24 @@ class GlassView : public View {
{{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},
@@ -222,7 +219,7 @@ class GlassView : public View {
{}};
ButtonWithEncoder button_beep_squelch{
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
{screen_width - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
""};
TextField field_marker{
@@ -293,15 +290,15 @@ class GlassView : public View {
}};
Button button_jump{
{SCREEN_W - 4 * 8, 5 * 16, 4 * 8, 16},
{screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
"JMP"};
Button button_rst{
{SCREEN_W - 9 * 8, 5 * 16, 4 * 8, 16},
{screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
"RST"};
Text freq_stats{
{0 * 8, 5 * 16, SCREEN_W - 10 * 8, 8},
{UI_POS_X(0), 5 * 16, screen_width - 10 * 8, 8},
""};
MessageHandlerRegistration message_handler_spectrum_config{
+4 -2
View File
@@ -202,7 +202,7 @@ void MicTXView::rxaudio(bool enable) {
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
break;
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 80k)
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
@@ -300,7 +300,7 @@ void MicTXView::update_receiver_rxbw(void) {
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
break;
case MIC_MOD_WFM:
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 80K(2)
break;
case MIC_MOD_AM:
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
@@ -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!
+2 -2
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};
@@ -350,7 +350,7 @@ class MicTXView : public View {
};
Button tx_button{
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
{10 * 8, screen_width, 10 * 8, 5 * 8},
"PTT TX",
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);
+8 -8
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, 30 * 8, 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,19 +133,19 @@ class PlaylistView : 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{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Text text_track{
{0 * 8, 3 * 16, 30 * 8, 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, 30 * 8, 16},
{UI_POS_X(0), 16 * 8, screen_width, 16},
""};
Text text_message_l2{
{0 * 8, 18 * 8, 30 * 8, 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};
+6 -6
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 {
@@ -160,7 +160,7 @@ class RDSView : public View {
void start_tx();
Rect view_rect = {0, 8 * 8, 240, 192};
Rect view_rect = {0, 8 * 8, screen_width, 192};
RDSPSNView view_PSN{nav_, view_rect};
RDSRadioTextView view_radiotext{nav_, view_rect};
@@ -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};
+8 -8
View File
@@ -335,7 +335,7 @@ ReconView::ReconView(NavigationView& nav)
tx_view.hidden(true);
// set record View
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -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();
@@ -1164,18 +1164,18 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
}
if (new_mod == SPEC_MODULATION) {
if (persistent_memory::recon_repeat_recorded()) {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"RECON_REPEAT.C16", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_as_is(true);
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_RAW", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_date_frequency(true);
}
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -1404,8 +1404,8 @@ void ReconView::start_repeat() {
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
// update display information
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Theme::getInstance()->fg_light->foreground);
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, *Theme::getInstance()->fg_red, delay_message);
p.fill_rectangle({0, (screen_height / 2) - 16, screen_width, 64}, Theme::getInstance()->fg_light->foreground);
p.draw_string({(screen_width / 2) - 7 * 8, screen_height / 2}, *Theme::getInstance()->fg_red, delay_message);
// sleep 1 second
chThdSleepMilliseconds(1000);
+23 -22
View File
@@ -217,35 +217,36 @@ 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
{0, 1 * 16, SCREEN_W, 16},
{0, 1 * 16, screen_width, 16},
};
Text desc_cycle{
{0, 2 * 16, SCREEN_W, 16},
{0, 2 * 16, screen_width, 16},
};
RSSI rssi{
{0 * 16, 3 * 16 + 2, SCREEN_W - 8 * 8 + 4, 12},
{0 * 16, 3 * 16 + 2, screen_width - 8 * 8 + 4, 12},
};
ButtonWithEncoder text_cycle{
@@ -285,19 +286,19 @@ class ReconView : public View {
// Button can be RECON or SCANNER
Button button_scanner_mode{
{SCREEN_W - 7 * 8, 3 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 3 * 16, 7 * 8, 28},
"RECON"};
Button button_loop_config{
{SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 5 * 16, 7 * 8, 28},
"[LOOP]"};
Button button_config{
{SCREEN_W - 7 * 8, 7 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 7 * 16, 7 * 8, 28},
"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_H / 2 - 16, SCREEN_W, 32}};
{UI_POS_X(0), screen_height / 2 - 16, screen_width, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
@@ -57,10 +57,6 @@
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
#define RECON_DEF_WAIT_DURATION 1000 // will be incremented/decremented by STATS_UPDATE_INTERVAL steps
// screen size helper
#define SCREEN_W 240
// #define SCREEN_H 320
// recon settings nb params
#define RECON_SETTINGS_NB_PARAMS 7
@@ -219,7 +215,7 @@ class ReconSetupView : public View {
std::string input_file{"RECON"};
std::string output_file{"RECON_RESULTS"};
Rect view_rect{0, 3 * 8, SCREEN_W, 230};
Rect view_rect{0, 3 * 8, screen_width, 230};
ReconSetupViewMain viewMain{nav_, view_rect, input_file, output_file};
ReconSetupViewMore viewMore{nav_, view_rect};
@@ -229,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"};
};
+6 -6
View File
@@ -46,15 +46,15 @@ class SdOverUsbView : public View {
NavigationView& nav_;
Labels labels{
{{3 * 8, 2 * 16}, "Click Run to start the", Theme::getInstance()->bg_darkest->foreground},
{{3 * 8, 3 * 16}, "USB Mass Storage Mode.", Theme::getInstance()->bg_darkest->foreground},
{{3 * 8, 5 * 16}, "It can take up to 20s", Theme::getInstance()->bg_darkest->foreground},
{{3 * 8, 6 * 16}, "for the drive to be", Theme::getInstance()->bg_darkest->foreground},
{{3 * 8, 7 * 16}, "available.", Theme::getInstance()->bg_darkest->foreground},
{{UI_POS_X_CENTER(22), 2 * 16}, "Click Run to start the", Theme::getInstance()->bg_darkest->foreground},
{{UI_POS_X_CENTER(22), 3 * 16}, "USB Mass Storage Mode.", Theme::getInstance()->bg_darkest->foreground},
{{UI_POS_X_CENTER(21), 5 * 16}, "It can take up to 20s", Theme::getInstance()->bg_darkest->foreground},
{{UI_POS_X_CENTER(19), 6 * 16}, "for the drive to be", Theme::getInstance()->bg_darkest->foreground},
{{UI_POS_X_CENTER(10), 7 * 16}, "available.", Theme::getInstance()->bg_darkest->foreground},
};
Button button_run{
{9 * 8, 15 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12), 15 * 16, 12 * 8, 3 * 16},
"Run"};
};
+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,
+7 -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"
@@ -759,7 +760,7 @@ AppSettingsView::AppSettingsView(
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)});
ensure_directory(settings_dir);
@@ -810,9 +811,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());
+61 -60
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",
};
@@ -372,11 +372,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 +391,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 +455,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 +498,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 +536,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 +559,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 +606,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 +649,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 +674,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 +683,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 +713,7 @@ class AppSettingsView : public View {
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
{0, 2 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)},
true};
};
@@ -738,11 +738,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 +784,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 +841,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 +876,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 +913,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,19 +941,16 @@ 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},
screen_width / 8,
{UI_POS_X(0), 4 * 16},
(size_t)(screen_width / 8),
{
{"Default - Grey", 0},
{"Yellow", 1},
{"Aqua", 2},
{"Green", 3},
{"Red", 4},
{"Dark", 5},
},
true};
@@ -962,8 +959,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",
};
};
@@ -987,10 +988,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,
@@ -1002,12 +999,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",
};
};
+2 -2
View File
@@ -57,7 +57,7 @@ void SIGFRXView::paint(Painter& painter) {
uint8_t i, xp;
// portapack::display.draw_bmp_from_bmp_hex_arr({0, 302-160}, fox_bmp);
portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
portapack::display.fill_rectangle({0, 16, screen_width, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
for (i = 0; i < 6; i++) {
xp = sigfrx_marks[i * 3];
painter.draw_string({(ui::Coord)sigfrx_marks[(i * 3) + 1], 144 - 20}, style_white, to_string_dec_uint(sigfrx_marks[(i * 3) + 2]));
@@ -66,7 +66,7 @@ void SIGFRXView::paint(Painter& painter) {
}
void SIGFRXView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
portapack::display.fill_rectangle({0, 144, 240, 4}, Theme::getInstance()->bg_darkest->foreground);
portapack::display.fill_rectangle({0, 144, screen_width, 4}, Theme::getInstance()->bg_darkest->foreground);
uint8_t xmax = 0, imax = 0;
size_t i;
+1 -1
View File
@@ -127,7 +127,7 @@ class SigGenView : public View {
{"Pulse CW 25%", 7}}};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
10000,
12};
+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{
{28 * 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,
+2 -2
View File
@@ -58,7 +58,7 @@ void ScreenshotViewer::paint(Painter& painter) {
}
// Screenshots from PNGWriter are all this size.
if (file.size() != 232383) {
if ((file.size() != 232383 && screen_width == 240) || (screen_width == 320 && file.size() != 463743)) {
show_invalid();
return;
}
@@ -66,7 +66,7 @@ void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
uint8_t buffer[buffer_size];
std::array<Color, screen_width> pixel_data;
std::vector<Color> pixel_data(screen_width);
// Seek past all the headers.
file.seek(43);
@@ -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_;
+5 -7
View File
@@ -84,6 +84,7 @@ void SubGhzDView::focus() {
SubGhzDView::SubGhzDView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
@@ -261,17 +262,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;
@@ -341,7 +339,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);
+15 -12
View File
@@ -122,15 +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, 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{};
@@ -149,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},
}};
@@ -188,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, 240, screen_height - (4 * 16) - 36}};
{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{
+2 -2
View File
@@ -99,8 +99,8 @@ void TestView::on_packet(const testapp::Packet& packet) {
display.draw_pixel(Point(cur_x, 4 * 16 + (256 - ((raw_alt - cal_value) / 4))), Color::white());
cur_x++;
if (cur_x >= 240) {
display.fill_rectangle(Rect(0, 5 * 16, 240, 256), Color::black());
if (cur_x >= screen_width) {
display.fill_rectangle(Rect(0, 5 * 16, screen_width, 256), Color::black());
cur_x = 0;
}
+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, 30 * 8, 16},
{UI_POS_X(0), 4 * 16, screen_width, 16},
"..."};
Text text_debug_b{
{0 * 8, 5 * 16, 30 * 8, 16},
{UI_POS_X(0), 5 * 16, screen_width, 16},
"..."};
Button button_cal{
+99 -21
View File
@@ -26,6 +26,7 @@
#include "log_file.hpp"
#include "string_format.hpp"
#include "irq_controls.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
paint_cursor(painter);
}
void TextViewer::enable_long_press() {
// Enable long press on "Select".
SwitchesState config;
config[toUType(Switch::Sel)] = true;
set_switches_long_press_config(config);
}
void TextViewer::on_focus() {
enable_long_press();
}
bool TextViewer::on_key(const KeyEvent key) {
int16_t delta_col = 0;
int16_t delta_line = 0;
if (key == KeyEvent::Left)
delta_col = -1;
else if (key == KeyEvent::Right)
delta_col = 1;
else if (key == KeyEvent::Up)
delta_line = -1;
else if (key == KeyEvent::Down)
delta_line = 1;
else if (key == KeyEvent::Select && on_select) {
on_select();
return true;
if (key == KeyEvent::Select) {
// Toggle 'digit' mode with long-press.
if (digit_mode_ || key_is_long_pressed(key)) {
digit_mode_ = !digit_mode_;
set_dirty();
return true;
}
}
if (digit_mode_) {
switch (key) {
case KeyEvent::Left:
delta_col = -1;
break;
case KeyEvent::Right:
delta_col = 1;
break;
case KeyEvent::Up:
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
break;
case KeyEvent::Down:
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
break;
default:
return false;
}
if (delta_col == 0 && delta_line == 0) {
return true;
}
} else {
switch (key) {
case KeyEvent::Left:
delta_col = -1;
break;
case KeyEvent::Right:
delta_col = 1;
break;
case KeyEvent::Up:
delta_line = -1;
break;
case KeyEvent::Down:
delta_line = 1;
break;
case KeyEvent::Select: {
if (on_select) {
on_select();
return true;
}
break;
}
default:
return false;
}
}
// Always allow cursor direction to be updated.
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
bool TextViewer::on_encoder(EncoderEvent delta) {
bool updated = false;
if (cursor_.dir == ScrollDirection::Horizontal)
updated = apply_scrolling_constraints(0, delta);
else {
delta *= 16;
updated = apply_scrolling_constraints(delta, 0);
if (digit_mode_) {
if (delta > 0) {
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
} else if (delta < 0) {
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
}
updated = true;
} else {
if (cursor_.dir == ScrollDirection::Horizontal) {
updated = apply_scrolling_constraints(0, delta);
} else {
delta *= 16;
updated = apply_scrolling_constraints(delta, 0);
}
if (updated) {
redraw();
}
}
if (updated)
redraw();
return updated;
}
void TextViewer::set_value(uint8_t new_value) {
if (new_value != value_) {
value_ = new_value;
if (on_change) {
on_change(value_);
}
set_dirty();
}
}
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
paint_state_.redraw_text = redraw_text;
paint_state_.redraw_marked = redraw_marked;
@@ -349,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,
@@ -534,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(
+12 -5
View File
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
std::function<void()> on_select{};
std::function<void()> on_cursor_moved{};
std::function<void(uint8_t)> on_change{};
void paint(Painter& painter) override;
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
void set_value(uint8_t new_value);
void on_focus() override;
void redraw(bool redraw_text = false, bool redraw_marked = false);
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
void cursor_set(uint16_t line, uint16_t col);
void cursor_mark_selected();
void cursor_clear_marked();
void enable_long_press();
typedef std::pair<uint16_t, uint16_t> LineColPair;
std::vector<LineColPair> lineColPair{};
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
int8_t char_height{};
uint8_t max_line{};
uint8_t max_col{};
bool digit_mode_{false};
uint8_t value_{0};
bool allow_digit_mode_{true};
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
@@ -157,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{
@@ -260,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}, "#"},
+40 -12
View File
@@ -107,6 +107,7 @@ void WeatherView::focus() {
WeatherView::WeatherView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
@@ -196,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:
@@ -224,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:
@@ -269,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) + "%" : "-";
@@ -301,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};
@@ -444,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
+21 -17
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,25 +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, UI_POS_WIDTH_REMAINING(24), 4},
};
AudioVolumeField field_volume{
{28 * 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{};
@@ -171,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},
@@ -215,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{
+33 -14
View File
@@ -93,8 +93,8 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
void WFMConfig::apply() const {
const WFMConfigureMessage message{
decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_80k_wfm_decim_0
decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_80k_wfm_decim_1
taps_64_lp_156_198, // Fixed channel audio filter 15khz
75000,
audio_48k_hpf_30hz_config,
@@ -105,10 +105,10 @@ void WFMConfig::apply() const {
void WFMAMConfig::apply() const {
const WFMAMConfigureMessage message{
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
decim_1, // Fixed 32 taps array : taps_84k_wfm_decim_1
taps_64_lp_1875_2166, // Fixed channel audio filter , 64 taps array , to filter DSB AM 2k4 carrier before demod. AM .
17000, // NOAA satellite tx , FM deviation = +-17Khz.
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
decim_1, // Dynamic 32 taps array : taps_80k_wfmam_decim_1, 38k_wfmam
taps_64_lp_bpf, // Dynamic 64 taps array , to filter modulated DSB AM 2k4 carrier before demod. AM .(LPF / BPF)
17000, // NOAA satellite tx , FM deviation = +-17Khz.
apt_audio_12k_notch_2k4_config,
apt_audio_12k_lpf_2000hz_config};
send_message(&message);
@@ -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,
@@ -186,13 +199,14 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
send_message(&message);
}
void set_fsk(const size_t deviation) {
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) {
const FSKRxConfigureMessage message{
taps_200k_decim_0,
taps_16k0_decim_1,
taps_11k0_channel,
2,
deviation};
samplesPerSymbol,
syncWord,
syncWordLength,
preamble,
preambleLength,
numDataBytes};
send_message(&message);
}
@@ -287,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);
}
@@ -327,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)};
+7 -3
View File
@@ -56,7 +56,7 @@ struct NBFMConfig {
};
struct WFMConfig {
const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 40K-
const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 80K-
const fir_taps_real<16> decim_1;
void apply() const;
@@ -65,6 +65,7 @@ struct WFMConfig {
struct WFMAMConfig {
const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
const fir_taps_real<32> decim_1;
const fir_taps_real<64> taps_64_lp_bpf; // to handle dynamically LPF / BPF .
void apply() const;
};
@@ -73,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 size_t deviation);
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);
@@ -90,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);
@@ -101,6 +104,7 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
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();
+38
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,
+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) {
+11
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
return (result);
}
int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
file_path = macaddress_dir / u"macaddress.db";
index_item_length = 7;
record_length = 64;
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
if (search_term.empty())
return DATABASE_RECORD_NOT_FOUND;
+8 -1
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -60,6 +61,12 @@ class database {
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
struct MacAddressDBRecord {
char vendor_name[64]; // vendor/manufacturer name
};
int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
private:
std::filesystem::path file_path = ""; // path including filename
int index_item_length = 0; // length of index item
@@ -77,4 +84,4 @@ class database {
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
};
#endif /*__DATABASE_H__*/
#endif /*__DATABASE_H__*/
+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();
+8 -8
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},
@@ -87,10 +87,10 @@ class ACARSAppView : public View {
true};
Console console{
{0, 3 * 16, 240, 256}};
{0, 3 * 16, screen_width, 256}};
AudioVolumeField field_volume{
{28 * 8, 1 * 16}};
{screen_width - 2 * 8, 1 * 16}};
std::unique_ptr<ACARSLogger> logger{};
+3 -3
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"};
};
@@ -218,7 +218,7 @@ class ADSBTxView : public View {
void start_tx();
void generate_frames();
Rect view_rect = {0, 7 * 8, 240, 192};
Rect view_rect = {0, 7 * 8, screen_width, 192};
ADSBPositionView view_position{nav_, view_rect};
ADSBCallsignView view_callsign{nav_, view_rect};
@@ -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
+10 -10
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{
{28 * 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{
@@ -105,7 +105,7 @@ class AFSKRxView : public View {
LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
Console console{
{0, 4 * 16, 240, screen_width}};
{0, 4 * 16, screen_width, screen_width}};
void on_data_afsk(const AFSKDataMessage& message);
+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, 30 * 8, 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,
@@ -62,7 +62,7 @@ class TimeScopeView : public View {
};*/
Waveform waveform{
{0, 1 * 16 + cursor_band_height, 30 * 8, 2 * 16},
{0, 1 * 16 + cursor_band_height, screen_width, 2 * 16},
audio_spectrum,
128,
0,
@@ -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, 30 * 8, 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__*/
@@ -86,10 +86,10 @@ class WhipCalcView : public View {
{13 * 8, 4 * 16, 10 * 16, 16},
"-"};
Console console{
{0, 6 * 16, 240, 160}};
{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"};
};
@@ -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) {
@@ -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},
+55
View File
@@ -0,0 +1,55 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui.hpp"
#include "ui_battleship.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::battleship {
void initialize_app(ui::NavigationView& nav) {
nav.push<BattleshipView>();
}
} // namespace ui::external_app::battleship
extern "C" {
__attribute__((section(".external_app.app_battleship.application_information"), used)) application_information_t _application_information_battleship = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::battleship::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Battleship",
/*.bitmap_data = */ {
// Pirate galleon - 16x16
0x80, 0x00, // ........#.......
0x80, 0x00, // ........#.......
0x80, 0x00, // ........#.......
0xC0, 0x01, // .......###......
0xE0, 0x03, // ......#####.....
0xF0, 0x07, // .....#######....
0xF8, 0x0F, // ....#########...
0xFC, 0x1F, // ...###########..
0xFE, 0x3F, // ..#############.
0x00, 0x01, // .......#........
0x00, 0x01, // .......#........
0x00, 0x01, // .......#........
0xFC, 0x3F, // ..############..
0xFE, 0x7F, // .##############.
0xFF, 0xFF, // ################
0xFC, 0x3F, // ..############..
},
/*.icon_color = */ ui::Color::blue().v,
/*.menu_location = */ app_location_t::GAMES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = */ {'P', 'P', 'O', '2'}, // Use POCSAG2 baseband (larger than FSKTX)
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}

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