Compare commits

...

101 Commits

Author SHA1 Message Date
gullradriel aef7c2be93 fix baseband (#2501) 2025-01-26 22:23:29 +01:00
gullradriel 4e38dc4d3e put back app in 'Utilities' (#2500)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-01-26 22:15:02 +01:00
Davide Rovelli 520ad97f78 Added different modulations in signal generator (#2492)
* Added DSB, AM 100% mod index and AM 50% mod index. Changed UI.
2025-01-26 21:41:11 +01:00
Lucas C. Villa Real 5bd208bca1 APRS: add frequency settings for Brazil (#2494)
* Add frequency settings for Brazil, named 'BR' that tunes to 145.570 MHz.
* Also added: Japan: 144.640 MHz (JAP), Thailand: 144.900 MHz (THA), Philippines: 144.740 MHz (PHI)
* Reordered list by increasing frequency
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
2025-01-26 21:39:38 +01:00
gullradriel a65ef3ce2e Externalize antenna calc and wav view (#2498)
* externalize antenna calc and wav view
* Added a tool to check if all the pictures in graphics are used in internal apps
2025-01-26 21:36:21 +01:00
gullradriel b2bb37af74 Flipper tx: use file_path, example file (#2496)
* added subghz_dir
* use subghz_dir from file_path
* example file
2025-01-26 17:46:43 +00:00
gullradriel 54ad82a423 fix fallthrough warning (#2497) 2025-01-26 16:27:29 +01:00
gullradriel e463405c77 fix cmake_minimum_required to 3.16 (#2499) 2025-01-26 16:26:02 +01:00
sommermorgentraum c413f925a9 Add fast flash script for sdcard switch hardware (#2480) 2025-01-20 12:43:57 +01:00
Erwin Ried 9e1eb31150 Update README.md
Discord badge was broken, switching to shields.io
2025-01-20 09:22:40 +01:00
sommermorgentraum 7eb08f34cb Fix encoder setting p.mem issue (#2475) 2025-01-09 15:56:14 +01:00
Erwin Ried 2de1f8e6bf Update README.md
Lab401 link with our redirection
2025-01-09 10:05:27 +01:00
sommermorgentraum 55db0e8c87 Add the feature to decide rotate direction of encoder (#2472) 2025-01-08 21:02:05 +01:00
gullradriel dfa35b4290 added discord server information (#2471) 2025-01-07 14:56:08 +01:00
gullradriel 0124051c4f fix for empty text prompt crash (#2468) 2025-01-07 13:46:51 +01:00
gullradriel 65f301399f disabling button on main menu, change labels and add 'icons' (#2466)
* disabling button on main menu, change labels and add 'icons'

* fix reverse order of buttons

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-01-04 10:50:53 +08:00
sommermorgentraum 97889494cf rename bitmaps into bmp 2025-01-03 14:33:30 +01:00
Erwin Ried 32b35b1507 Update README.md 2025-01-02 15:27:13 +01:00
quantum-x fccff0484b Update README.md (#2456)
Added a link to Lab401.com as a purchase option for EU customers.
(Lab401 was added as the EU exclusive distributor for the H4M - https://opensourcesdrlab.com/pages/distributors)
2025-01-02 15:22:58 +01:00
OpenSourceSDRLab d94afe364b The gerber files of the portapack H4 (#2463)
* Create README.txt

* Update README.txt

* Add files via upload

The gerber files of the portapack h4.

* delete

* Upload the gerber files for H4
2025-01-02 09:31:28 +13:00
Erwin Ried 2e4db3dd27 Update README.md
New metal case link
2024-12-31 22:39:35 +01:00
E.T. 97d3edbde7 docker improvements (#2455) 2024-12-31 11:27:38 +01:00
gullradriel 4c18b80e42 Navigation buttons (#2458)
* regenerate bitmap data
* pagination in submenu
* using little font so we are not eating menu buttons
2024-12-31 10:01:03 +08:00
Mark Thompson 01034af077 Renamed parameters in rx_capture.ini file (#2452)
* Rename settings in file to match screen
* Renamed variables for hopefully better clarity
2024-12-29 18:29:31 +01:00
Mark Thompson 57ce978bab Remember previous capture settings (#2450) 2024-12-29 09:21:08 +01:00
Totoo 854ac514cb M10 additional parser (#2448) 2024-12-28 12:47:58 +01:00
sommermorgentraum b47a5fe0c8 Add + - buttons in Encoder dial settings (#2447) 2024-12-27 22:04:11 +01:00
sommermorgentraum af362600ef app manager (#2442)
* fix unset autostart
* clean up - add comments to prevent misleading
* move the app to external and with necessary changes
* replace autostart app
2024-12-27 12:01:33 +01:00
hackrfstuff bad57d1391 Fake brightness reimplementation (#2444)
* Fake brightness reimplementation
* indentation
* added call to the function which is caching the display settings values
* use cached values instead of pmem
2024-12-26 23:23:02 +01:00
Erwin Ried ef28e63a8c Update README.md
Actually valid complete ali h4m link
2024-12-21 01:36:49 +01:00
jLynx eb22af84ff Update version.txt 2024-12-20 20:05:01 +13:00
jLynx 6dc25813e2 Update past_version.txt 2024-12-20 20:04:46 +13:00
Bernd Herzog 4ee00c0031 Enable exFAT feature (#2437)
* enabled exFAT in ffconf
* added exFAT to sd card check
* solved todo
2024-12-20 07:55:39 +01:00
sommermorgentraum 55525ed4ad fix wrong cast in morse app (#2436)
* fix wrong cast

* sorry, forgot to use enum
2024-12-20 07:53:15 +01:00
Erwin Ried 84dcfe29a7 Update README.md link to h4m was broken again 2024-12-18 23:44:35 +01:00
Erwin Ried 1ebe956557 h4m photo was broken 2024-12-18 23:35:42 +01:00
gullradriel ae656f07bf Usb fix (#2433)
* revert custom compiler definition since we are now having correct hackrf m0 implementation

* update submodule

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-12-18 21:55:53 +01:00
sommermorgentraum d5c894b322 fix namingspace for an app, remove too old images and links (#2432) 2024-12-18 11:20:53 +01:00
sommermorgentraum 9cea76a9f3 add metronome app (merge tuner firstly to satisfy conflict) (#2431)
* _

* _
2024-12-18 09:25:02 +00:00
sommermorgentraum ead9449609 add tuner app (#2429)
* _

* icon
2024-12-18 10:06:25 +01:00
Totoo fff63e056a FM Radio ext app (#2428) 2024-12-17 18:53:26 +01:00
sommermorgentraum 42da7443b4 fix gray box & remove multiple line text support for NewButton (#2426) 2024-12-15 18:22:19 +01:00
gullradriel ee10081854 update submodule (#2424) 2024-12-14 18:21:13 +00:00
gullradriel b38d1012fa Fix usb.c and usb_queue.c warnings in our CMakeLists instead of in the files (#2423) 2024-12-14 19:14:39 +01:00
sommermorgentraum 5075349d2d sdcard format detect and warn user (#2420) 2024-12-14 09:39:39 +01:00
gullradriel 8497b62c13 updated submodule (#2422)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-12-14 09:38:45 +01:00
jLynx fdeaf380d1 Update README.md 2024-12-09 20:41:43 +13:00
gullradriel 67eb8e9104 update submodule (#2413) 2024-12-07 15:43:16 +01:00
sommermorgentraum 989414d4df prevent show 1970 when focusing the paging item in fileman app (#2412)
* _

* fix map view button overlap
2024-12-07 13:13:06 +01:00
sommermorgentraum 509bda2472 move Temprature debug app into external (#2411)
* remove commented out code
2024-12-07 12:38:15 +01:00
Erwin Ried f5e6cb0d96 Update README.md
Removing the upper warning of scams, since it has been calm last half year and temporal link after another H2 item was taken down
2024-12-07 11:01:41 +01:00
Bernd Herzog 8e2442f196 Add fresh build of hackrf firmware to firmware zip (#2409)
* added fresh build of hackrf firmware to firmware zip

* refactoring

* refactoring
2024-12-06 13:02:00 +01:00
sommermorgentraum ac89ca2a2a RSSI draw/ Level app opt (#2403)
* change peak from green to orange
* add db
2024-12-05 19:24:27 +01:00
gullradriel 498369b77d updated submodule (#2407) 2024-12-05 18:19:30 +01:00
Bernd Herzog 2e64fae953 updated submodule (#2405) 2024-12-05 13:18:33 +01:00
Bernd Herzog 2d8e0f88a3 Replace hackrf submodule (#2404)
* replaced hackrf submodule
* updated hackrf submodule
* replaced hackrf overlay with mayhem fork
2024-12-05 12:31:45 +01:00
Bernd Herzog 874eba8b36 Add portapack cpld write usb serial command for AG256SL100 devices (#2401) 2024-12-05 08:12:14 +01:00
cygeus c553df7170 Corrected two small typos. (#1932) 2024-11-30 21:49:33 +13:00
sommermorgentraum 67a804c5ba fix progress bar still ongoing after user interraced, in OOKEdit app (#2397)
* _

* _

* format
2024-11-28 09:39:02 +01:00
Erwin Ried 9a14d04c91 Update README.md
Mistake
2024-11-27 10:08:38 +01:00
Erwin Ried e17aef3724 New techminds video :) 2024-11-27 10:08:14 +01:00
gullradriel 4155216a7f added ppfw to sdcard/FIRMWARE (#2395)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-11-27 06:57:05 +13:00
E.T. d9bc542b06 Upgrade build container (#2394)
* Upgrade build container os version

* Also updare the arm build container
2024-11-25 21:19:43 +13:00
gullradriel 3dcfa4f8c1 use full_reset_and_init return value in init and reset_learned (#2393)
* use full_reset_and_init return value in init and reset_learned

* do not uncomment status clear

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-11-25 21:01:16 +13:00
gullradriel d36ae88284 Changed portapack-h1_h2-mayhem to portapack-mayhem-firmware (#2390)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2024-11-24 23:24:14 +08:00
E.T. 69271632ae Restore home menu order (#2384)
* Fix ext notice position ( No need to alter the position of the ext app notice, as there is no back button on the home screen )
* add desired position to external apps
* read and store desired location
* apply ext apps desired order
* fix memory alignment in application_information_t
2024-11-23 21:37:03 +01:00
gullradriel e6a099913a Update about file from generated one (#2382)
* added launch permission
* updated about file with script
2024-11-23 00:41:20 +08:00
Benjamin Møller 280b8afc0a Add changes from "fixed memory usage #2380" to about_simple file generator. (#2381)
* Generator for updating the about-page in PP from github.
* Rename generate_ui-about-simple.ccp.py to generate_ui-about-simple.cpp.py
* Adapt changes from "fixed memory usage #2380" to the file generator.
2024-11-22 17:12:17 +01:00
Bernd Herzog b108d975c0 fixed memory usage (#2380)
* fixed memory usage in ui_about_simple
* fixed memory usage in freqman_db
* fixed memory usage in tone_key
2024-11-22 09:54:34 +01:00
gullradriel fc08d4ead9 renaming Ook to OOK (#2379) 2024-11-21 21:24:40 +01:00
Totoo 07cae4a298 Remove warn text too from batt (#2377) 2024-11-20 15:02:02 +01:00
sommermorgentraum e88e0b5f8f splash fix and draw bmp fuc logic wrong (#2376)
* revert

* rename drawBMP to draw_bmp_from_bmp_hex_arr

* rename drawBMP2

* add comments

* rename BMPView class in child of nav into SplashView

* rename SplashView class in child of nav into SplashScreeView

* fix draw from wrong line

* comment

* comment

* comment
2024-11-20 17:35:40 +08:00
Totoo 131523d726 Csv from subghzd (#2375) 2024-11-20 09:22:55 +01:00
gullradriel 24d15c1643 Externalize Remote (#2370)
* externalize Remote app, disabling fileman integration (need workaround)

* regenerate bitmap.hpp

* added external HOME apps to HOME
2024-11-19 21:02:29 +01:00
Totoo 4a83118557 Weather csv log (#2369) 2024-11-19 17:39:30 +01:00
gullradriel ff54f59d38 Ook Editor adjustements (#2366)
* adding ook_file.cpp

* aligned GUI, change low_bit to pause, start to use ook_file API

* bit duration, pause, fields step, indent

* working OOK file loading

* revamped GUI with the decided names and units

* read, start tx, stop tx in ook_file API

* cleanings, using ook_file_data and API to load, save, send, stop tx

* renamed text widget

* moved set target frequency to start_ook_file_tx function

* comments, took out a function to put it in API

* removed non working Tesla OOK files

* changed space to tab

* set author information

* Renamed to OOKEditor
2024-11-19 17:16:37 +01:00
Erwin Ried 6b8274d792 Update README.md
Direct link to opensourcesdrlabs device
2024-11-19 15:49:38 +01:00
Totoo 1fc84e7eac Hide cycles until 0 error resolved (#2367) 2024-11-19 11:23:33 +01:00
gullradriel d31857c21e Ook remote adjustements (#2365)
* moved to ook_remote dir

* moved to new filenames / dir

* adjusted build files to new dir/filenames

* changed name of var / dirname

* deleted wrongly included swap files

* Added 'Set' 'Open File'

* Use language API, make it so TX can be started and also stopped

* added send, recv

* fixed the name which was too long

* renamed some fields, moved the ui, added some text informations, renames some variables

* changed to FrequencyField, allow on change, on edit, commented out button_save for a next pass

* added OOK files examples

* added button_save feature
2024-11-18 14:51:49 +01:00
Erwin Ried 52b2cb241a Grabifyed link to opensdrlabs h4m 2024-11-18 09:55:48 +01:00
Samir Sánchez Garnica 64f8158134 External App ROOK(Remote On-Off Keying) (#2363) 2024-11-17 21:23:56 +01:00
Totoo 59f72cbff1 Flipper sub (raw+binraw AND ONLY OOK) TX (#2361) 2024-11-16 18:03:53 +01:00
Erwin Ried 31c53dc455 Update README.md (#2362) 2024-11-15 14:46:30 +01:00
Erwin Ried ff12d06757 Update links README.md
(direct link to opensourcesdrlab for now, without grabify tracking due technical issue)
2024-11-15 13:38:49 +01:00
Erwin Ried 5b540fa659 Update README.md 2024-11-14 22:39:58 +01:00
Erwin Ried 9cccfa63e5 Update README.md 2024-11-14 22:35:32 +01:00
sommermorgentraum 6c838256df auto scroll the name list (#2358)
* auto scroll the name list

* script

* remove uneeded empty line in script

* script
2024-11-14 14:52:43 +01:00
RocketGod c8f236a708 CVS Spam v0.2 (#2357)
* Make the CVS Spam app

CHAOS.C16 is over 100mb and send to JLynx to be included in the sdcard ZIP file.

* indentation

* C16 files are perfect. Disregard any previous files for sdcard/CVSFILES. CHAOS now randomizes files to save space.

---------

Co-authored-by: sommermorgentraum <24917424+zxkmm@users.noreply.github.com>
2024-11-14 15:06:52 +08:00
Totoo 4641dcb449 Ookbrute (#2354) 2024-11-11 17:05:19 +01:00
gullradriel 458a6634bf Splash resize (#2353)
* resized default splash screens to maximum displayed size
* fix splash screen X,Y position to 0,16
2024-11-09 15:12:39 +01:00
RocketGod b8a64598ba Make the CVS Spam app (#2352) 2024-11-08 22:26:04 +01:00
RocketGod 09dff447de Create the Shopping Cart Lock app (#2326) 2024-11-08 16:10:25 +01:00
Erwin Ried 079329fc6c Update README.md
Wrong template was linked
2024-11-08 15:36:21 +01:00
Erwin Ried 674059801d Update front videos and links on README.md (#2351)
Some links were broken again and lets promote RocketGod's video :)
2024-11-08 14:41:55 +01:00
Totoo 5020e5bd28 I2cshell (#2348)
I2C shell implementation.
2024-11-08 11:46:27 +01:00
Totoo c31fef0535 Removed fake brightness (#2349) 2024-11-08 15:47:22 +08:00
Totoo a153cd741d remove the only 1 stoi (#2341) 2024-11-08 08:12:50 +01:00
sommermorgentraum 5851b47f99 add text for ccache in makefile to prevent confusing Ninja user (#2346)
* add necessary text for ccache in makefile to prevent confusing Ninja user

* typo fix

* textual
2024-11-07 23:02:52 +08:00
Brumi-2021 83a488e5b9 Auto switch C16/C8 default REC format in both BW directions. (#2343) 2024-11-07 10:55:32 +08:00
gullradriel 110a543ed3 save and restore region and or manual frequency values (#2340) 2024-11-05 14:56:38 +01:00
Benjamin Møller da5322765d Add about_simple updater from github (#2328)
* Generator for updating the about-page in PP from github.

* Fixing typo

* Rename generate_ui-about-simple.ccp.py to generate_ui-about-simple.cpp.py

Rename file, fix typo
2024-11-05 10:41:08 +08:00
319 changed files with 58386 additions and 2443 deletions
+2 -1
View File
@@ -1 +1,2 @@
build
*
!firmware/tools/docker-entrypoint.sh
+6 -3
View File
@@ -54,19 +54,22 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
- name: Prepare Firmware ZIP
run: |
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
- name: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6 -3
View File
@@ -38,19 +38,22 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
- name: Prepare Firmware ZIP
run: |
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
- name: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -1 +1 @@
v2.0.1
v2.0.2
+1 -1
View File
@@ -1 +1 @@
v2.0.2
v2.1.0
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "hackrf"]
path = hackrf
url = https://github.com/mossmann/hackrf.git
url = https://github.com/portapack-mayhem/hackrf.git
+5 -4
View File
@@ -18,7 +18,7 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.16)
cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
@@ -65,10 +65,11 @@ message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
add_subdirectory(hackrf-portapack)
add_subdirectory(hackrf/firmware)
set(HACKRF_FIRMWARE_DFU_FILENAME hackrf_usb.dfu)
set(HACKRF_FIRMWARE_BIN_FILENAME hackrf_usb_ram.bin)
set(HACKRF_FIRMWARE_FILENAME hackrf_usb.bin)
set(HACKRF_CPLD_XSVF_FILENAME default.xsvf)
set(HACKRF_PATH ${CMAKE_CURRENT_LIST_DIR}/hackrf)
@@ -88,12 +89,12 @@ if(USE_CCACHE)
if(CCACHE_FOUND)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_FOUND})
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_FOUND})
message(STATUS "Using ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary.\n-- use `cmake -DUSE_CCACHE=OFF ..` to turn off ccache")
message(STATUS "Using ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary.\n-- use `cmake -DUSE_CCACHE=OFF ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=OFF .. ` (cmake with Ninja) to turn off ccache")
else()
message(WARNING "ccache is enabled but not found on the system.")
endif()
else()
message(STATUS "Not using ccache, use `cmake -DUSE_CCACHE=ON ..` to turn on ccache")
message(STATUS "Not using ccache, use `cmake -DUSE_CCACHE=ON ..` (pure cmake) or `cmake -G Ninja -DUSE_CCACHE=ON ..` (cmake with Ninja) to turn on ccache")
endif()
message(STATUS "-------^ ccache info--------")
+11 -12
View File
@@ -1,25 +1,23 @@
<!---
> [!WARNING]
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED VERSIONS, YOU'RE BEING SCAMMED.__
>
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.
> The only legitimate link to our repositories is the [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware) organization on GitHub.--->
# PortaPack Mayhem
[![Nightly Release](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases) [![GitHub Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/latest/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](https://discord.gg/tuwVMv3)
[![Nightly Release](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases) [![GitHub Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/latest/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://img.shields.io/discord/719669764804444213?label=Discord)](https://discord.gg/tuwVMv3)
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
*[PortaPack H2+HackRF+battery](https://grabify.link/ZYYPLB) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
[<img src="https://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition) [<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
# What is this?
If you are new to *HackRF+PortaPack+Mayhem*, check these:
[<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="701">](https://grabify.link/0JUHZ6)
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="701">](https://share.hackrf.app/6HKX9A)
[<img alt="Its TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://grabify.link/X4D5TF) [<img alt="What is the HackRF One Portapack H2+?" src="https://img.youtube.com/vi/alrFbY5vxt4/maxresdefault.jpg" width="172">](https://grabify.link/9UZGEW) [<img alt="Beginner's Guide To The HackRF & Portapak With Mayhem" src="https://img.youtube.com/vi/H-bqdWfbhpg/maxresdefault.jpg" width="172">](https://grabify.link/5MU2VH) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://grabify.link/C0J6ZR)
[<img alt="Its TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF) [<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="172">](https://share.hackrf.app/F9MPOO) [<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="172">](https://share.hackrf.app/0JUHZ6) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://share.hackrf.app/C0J6ZR)
# Frequently Asked Questions
@@ -27,11 +25,12 @@ This repository expands upon the previous work by many people and aims to consta
## What to buy?
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous [PortaPack H4M Mayhem](https://grabify.link/VPMPSL), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://grabify.link/99SAMT). Join their giveaways on discord (check the badge on top).
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/HR60Q4) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://grabify.link/ZYYPLB), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://grabify.link/XNGTAP), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/9FJFJE), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
@@ -55,7 +54,7 @@ Consider that the hardware and firmware has been created and maintain by a [lot]
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
## What if I really want something specific?
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?labels=enhancement&template=feature_request.md). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?assignees=&labels=enhancement&projects=&template=02_feature_request.yml). Alternatively, go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
## What if I need help?
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
+2 -11
View File
@@ -1,4 +1,4 @@
FROM ubuntu:xenial
FROM ubuntu:noble
# Set location to download ARM toolkit from.
# This will need to be changed over time or replaced with a static link to the latest release.
@@ -11,19 +11,10 @@ WORKDIR /havoc/firmware
# Fetch dependencies from APT
RUN apt-get update \
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get install -y git tar wget dfu-util cmake python3 python3-pip python3-yaml ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
#Install current pip from PyPa
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
python3 get-pip.py
#Fetch additional dependencies from Python 3.x pip
RUN pip install pyyaml \
&& ln -s /usr/bin/python3 /usr/bin/python \
&& ln -s /usr/bin/pip3 /usr/bin/pip
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
+2 -11
View File
@@ -1,4 +1,4 @@
FROM ubuntu:xenial
FROM ubuntu:noble
# Set location to download ARM toolkit from.
# This will need to be changed over time or replaced with a static link to the latest release.
@@ -11,19 +11,10 @@ WORKDIR /havoc/firmware
# Fetch dependencies from APT
RUN apt-get update \
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get install -y git tar wget dfu-util cmake python3 python3-pip python3-yaml ccache bzip2 liblz4-tool curl ninja-build \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
#Install current pip from PyPa
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
python3 get-pip.py
#Fetch additional dependencies from Python 3.x pip
RUN pip install pyyaml \
&& ln -s /usr/bin/python3 /usr/bin/python \
&& ln -s /usr/bin/pip3 /usr/bin/pip
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
Executable
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
#################################################
# This script aids building mayhem inside docker
#
# Basic usage:
# - Build dev container: ./dockerize.sh build
# - Build mayhem: ./dockerize.sh
#
# The image will be automatically build if it
# does not exist, but if the dockerfile changes
# it need to be rebuilt manually.
#
# Advanced parameters:
# - Get a shell inside the build image to
# inspect problems: ./dockerize.sh shell
# - Give additional parameters to the container:
# ./dockerize.sh -j10
# ./dockerize.sh ninja -j10
# - Use a different dockerfile:
# ./dockerize.sh build dockerfile-other
# - Use a different cpu architecture:
# ./dockerize.sh build dockerfile-nogit-arm arm64
#
# Environment variables:
# - It is possible to override the default image
# name that is being used to build and run the
# build container using an environment variable.
# The default is 'portapack-dev'
# Override by setting the following environment
# variable: MAYHEM_DEV_DOCKER_IMAGE
#
#
# Copyright (C) 2024 u-foka
#
# 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.
#
set -e # exit immediatelly on any failure
DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
IMAGE="${MAYHEM_DEV_DOCKER_IMAGE:-portapack-dev}"
build_image() {
DOCKERFILE=${1:-dockerfile-nogit}
PLATFORM=${2:-amd64}
docker build --platform "linux/${PLATFORM}" -t "${IMAGE}" -f "${DOCKERFILE}" .
}
start_docker() {
if [ -z "$(docker images -q ${IMAGE} 2> /dev/null)" ]; then
build_image
fi
exec docker run -v "${DIR}:/havoc" -u "$(id -u):$(id -g)" -ti --rm "${IMAGE}" "$@"
}
if [ "$1" = 'shell' ]; then # open a shell into the container
start_docker "bash -li"
elif [ "$1" = 'build' ]; then # build the default (or specified) target with ninja
shift # remove the first item from $@ as we consumed it, we can then pass the rest on to make
build_image "$@"
exit $?
fi
start_docker "$@"
+3 -3
View File
@@ -18,7 +18,7 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.16)
project(firmware)
@@ -33,7 +33,7 @@ set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
set(LZ4 lz4)
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
set(FIRMWARE_NAME portapack-mayhem-firmware)
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
@@ -64,7 +64,7 @@ add_custom_command(
add_custom_target(
firmware
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME} ${HACKRF_FIRMWARE_FILENAME}
)
if(${GCC_VERSION_MISMATCH})
+3 -4
View File
@@ -117,6 +117,7 @@ set(CSRC
usb_serial_descriptor.c
usb_serial_endpoints.c
usb_serial_device_to_host.c
i2c_device_to_host.c
${HACKRF_PATH}/firmware/common/usb.c
${HACKRF_PATH}/firmware/common/usb_queue.c
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
@@ -243,6 +244,7 @@ set(CPPSRC
hw/si5351.cpp
hw/spi_pp.cpp
hw/touch_adc.cpp
ook_file.cpp
ui_baseband_stats_view.cpp
ui_navigation.cpp
ui_record_view.cpp
@@ -301,7 +303,6 @@ set(CPPSRC
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
apps/ui_remote.cpp
apps/ui_scanner.cpp
apps/ui_sd_over_usb.cpp
apps/ui_sd_wipe.cpp
@@ -316,9 +317,7 @@ set(CPPSRC
apps/ui_text_editor.cpp
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_weatherstation.cpp
apps/ui_whipcalc.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
@@ -376,7 +375,7 @@ set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC
hw
apps
protocols
bitmaps
bmps
)
#
+43 -16
View File
@@ -39,6 +39,14 @@ using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
#define BLE_RX_NO_ERROR 0
#define BLE_RX_LIST_FILENAME_EMPTY_ERROR 1
#define BLE_RX_ENTRY_FILENAME_EMPTY_ERROR 2
#define BLE_RX_LIST_SAVE_ERROR 3
#define BLE_RX_ENTRY_SAVE_ERROR 4
static uint8_t ble_rx_error = BLE_RX_NO_ERROR;
void BLELogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
@@ -202,19 +210,23 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
}
void BleRecentEntryDetailView::on_save_file(const std::string value, BLETxPacket packetToSave) {
ensure_directory(packet_save_path);
auto folder = packet_save_path.parent_path();
auto ext = packet_save_path.extension();
auto new_path = folder / value + ext;
saveFile(new_path, packetToSave);
if (value.length() > 0) {
ensure_directory(packet_save_path);
auto folder = packet_save_path.parent_path();
auto ext = packet_save_path.extension();
auto new_path = folder / value + ext;
ble_rx_error = saveFile(new_path, packetToSave);
} else {
nav_.pop();
ble_rx_error = BLE_RX_ENTRY_FILENAME_EMPTY_ERROR;
}
}
bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETxPacket packetToSave) {
File f;
auto error = f.create(path);
if (error)
return false;
return BLE_RX_ENTRY_SAVE_ERROR;
std::string macAddressStr = packetToSave.macAddress;
std::string advertisementDataStr = packetToSave.advertisementData;
@@ -224,7 +236,7 @@ bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETx
f.write(packetString.c_str(), packetString.length());
return true;
return BLE_RX_NO_ERROR;
}
void BleRecentEntryDetailView::update_data() {
@@ -568,11 +580,14 @@ std::string BLERxView::build_line_str(BleRecentEntry entry) {
}
void BLERxView::on_save_file(const std::string value) {
auto folder = packet_save_path.parent_path();
auto ext = packet_save_path.extension();
auto new_path = folder / value + ext;
saveFile(new_path);
if (value.length() > 0) {
auto folder = packet_save_path.parent_path();
auto ext = packet_save_path.extension();
auto new_path = folder / value + ext;
ble_rx_error = saveFile(new_path);
} else {
ble_rx_error = BLE_RX_LIST_FILENAME_EMPTY_ERROR;
}
}
bool BLERxView::saveFile(const std::filesystem::path& path) {
@@ -584,7 +599,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
auto error = src->open(path, false, true);
if (error) {
return false;
return BLE_RX_LIST_SAVE_ERROR;
}
for (const auto& entry : recent) {
@@ -615,7 +630,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
auto error = dst->open(tempFilePath, false, true);
if (error) {
return false;
return BLE_RX_LIST_SAVE_ERROR;
}
dst->write_line(headerStr.c_str());
@@ -695,7 +710,7 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
tempList.clear();
return true;
return BLE_RX_NO_ERROR;
}
void BLERxView::on_data(BlePacketData* packet) {
@@ -831,6 +846,18 @@ void BLERxView::on_timer() {
baseband::set_btlerx(randomChannel);
}
}
if (ble_rx_error != BLE_RX_NO_ERROR) {
if (ble_rx_error == BLE_RX_LIST_FILENAME_EMPTY_ERROR) {
nav_.display_modal("Error", "List filename is empty !");
} else if (ble_rx_error == BLE_RX_ENTRY_FILENAME_EMPTY_ERROR) {
nav_.display_modal("Error", "Entry filename is empty !");
} else if (ble_rx_error == BLE_RX_LIST_SAVE_ERROR) {
nav_.display_modal("Error", "Couldn't save list !");
} else if (ble_rx_error == BLE_RX_ENTRY_SAVE_ERROR) {
nav_.display_modal("Error", "Couldn't save entry !");
}
ble_rx_error = BLE_RX_NO_ERROR;
}
}
void BLERxView::handle_entries_sort(uint8_t index) {
+15 -10
View File
@@ -55,22 +55,24 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
this->field_frequency.set_step(v);
};
option_format.set_selected_index(0); // Default to C16
option_format.set_selected_index(file_format);
option_format.on_change = [this](size_t, uint32_t file_type) {
file_format = file_type;
record_view.set_file_type((RecordView::FileType)file_type);
};
check_trim.set_value(trim);
check_trim.on_select = [this](Checkbox&, bool v) {
trim = v;
record_view.set_auto_trim(v);
};
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
option_bandwidth.on_change = [this](size_t, uint32_t new_capture_rate) {
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
* provides 2 values (I,Q), the sample_rate is equal to bandwidth here. */
auto sample_rate = bandwidth;
/* base_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
/* capture_rate (bandwidth) is used for FFT calculation and display LCD, and also in recording writing SD Card rate. */
/* ex. sampling_rate values, 4Mhz, when recording 500 kHz (BW) and fs 8 Mhz, when selected 1 Mhz BW ... */
/* ex. recording 500kHz BW to .C16 file, base_rate clock 500kHz x2(I,Q) x 2 bytes (int signed) =2MB/sec rate SD Card. */
@@ -78,7 +80,7 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
// record_view determines the correct oversampling to apply and returns the actual sample rate.
// NB: record_view is what actually updates proc_capture baseband settings.
auto actual_sample_rate = record_view.set_sampling_rate(sample_rate);
auto actual_sample_rate = record_view.set_sampling_rate(new_capture_rate);
// Update the radio model with the actual sampling rate.
receiver_model.set_sampling_rate(actual_sample_rate);
@@ -87,17 +89,20 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz
if ((bandwidth >= 1500000) && (previous_bandwidth < 1500000)) {
option_format.set_selected_index(1);
// Automatically switch default capture format to C8 when bandwidth setting is increased to >=1.5MHz anb back to C16 for <=1,25Mhz
if ((new_capture_rate >= 1500000) && (capture_rate < 1500000)) {
option_format.set_selected_index(1); // Default C8 format for REC, 1500K ... 5500k
}
previous_bandwidth = bandwidth;
if ((new_capture_rate <= 1250000) && (capture_rate > 1250000)) {
option_format.set_selected_index(0); // Default C16 format for REC , 12k5 ... 1250K
}
capture_rate = new_capture_rate;
waterfall.start();
};
receiver_model.enable();
option_bandwidth.set_by_value(500000);
option_bandwidth.set_by_value(capture_rate);
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
+11 -2
View File
@@ -48,12 +48,21 @@ class CaptureAppView : public View {
private:
static constexpr ui::Dim header_height = 3 * 16;
uint32_t previous_bandwidth{500000};
uint32_t capture_rate{500000};
uint32_t file_format{0};
bool trim{false};
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
app_settings::SettingsManager settings_{
"rx_capture", app_settings::Mode::RX};
"rx_capture",
app_settings::Mode::RX,
{
{"capture_rate"sv, &capture_rate},
{"file_format"sv, &file_format},
{"trim"sv, &trim},
}};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
+83 -45
View File
@@ -1,55 +1,63 @@
#include "ui_about_simple.hpp"
#include <string_view>
#define ROLL_SPEED_FRAME_PER_LINE 60
// cuz frame rate of pp screen is probably 60, scroll per sec
namespace ui {
// TODO: Generate this automatically from github
// Information: a line starting with a '#' will be yellow coloured
const std::string authors_list[] = {
"# * List of contributors * ",
constexpr std::string_view mayhem_information_list[] = {
"#****** Mayhem Community ******",
" ",
"#Mayhem:",
"eried,euquiq,gregoryfenton",
"johnelder,jwetzell,nnemanjan00",
"N0vaPixel,klockee,gullradriel",
"jamesshao8,ITAxReal,rascafr",
"mcules,dqs105,strijar,fossum",
"zhang00963,RedFox-Fr,aldude999",
"East2West,ArjanOnwezen,jLynx",
"vXxOinvizioNxX,teixeluis,n0xa",
"Brumi-2021,texasyojimbo,zigad",
"heurist1,intoxsick,ckuethe",
"notpike,MichalLeonBorsuk",
"kallanreed,bernd-herzog,zxkmm",
"NotherNgineer,jimilinuxguy",
"iNetro,HTotoo,BehleZebub,dc2dc",
"arneluehrs,u-foka,Maescool",
"nnesetto,formtapez,F33RNI",
"rusty-labs,andrej-mk,taskinen",
"MatiasFernandez,TQMatvey,KimIV",
"RobertoD91,RndmNmbr,OpCode1300",
"alex10791,GitClo,GermanAizek",
"JimiHFord,jstockdale,aSmig",
"KillerTurtleSoftware,Lpd738",
"MattLodge,z3r0l1nk,joyel24",
" https://discord.mayhem.app",
" ",
"#**** List of contributors ****",
" ",
"#Mayhem-Firmware:",
"jboone,eried,furrtek,",
"NotherNgineer,gullradriel,",
"jLynx,kallanreed,Brumi-2021,",
"htotoo,zxkmm,bernd-herzog,",
"ArjanOnwezen,euquiq,u-foka,",
"iNetro,heurist1,dqs105,",
"teixeluis,jwetzell,",
"jimilinuxguy,gregoryfenton,",
"notpike,strijar,BehleZebub,",
"arneluehrs,mcules,rascafr,",
"joyel24,ImDroided,zigad,",
"johnelder,klockee,nnesetto,",
"LupusE,argilo,dc2dc,formtapez,",
"RocketGod-git,mrmookie,",
"ITAxReal,F33RNI,F4GEV,",
"rusty-labs,mjwaxios,andrej-mk,",
"RedFox-Fr,nemanjan00,",
"MichalLeonBorsuk,",
"MatiasFernandez,Giorgiofox",
" ",
"#Havoc:",
"furrtek,mrmookie,NotPike",
"mjwaxios,ImDroided,Giorgiofox",
"F4GEV,z4ziggy,xmycroftx",
"troussos,silascutler",
"nickbouwhuis,msoose,leres",
"joakar,dhoetger,clem-42",
"brianlechthaler,ZeroChaos-...",
"jboone,furrtek,eried,argilo,",
"mrmookie,Giorgiofox,ImDroided,",
"mjwaxios,F4GEV,OpCode1300,",
"ZeroChaos-,RndmNmbr,",
"silascutler,troussos,z4ziggy,",
"clem-42,dhoetger,NickBouwhuis,",
"xmycroftx,Maescool,KimIV,",
"joakar,leres,brianlechthaler,",
"N0vaPixel",
" ",
"#PortaPack:",
"jboone,argilo",
"jboone,mossmann,martinling,",
"argilo,eried,ZeroChaos-,",
"RndmNmbr",
" ",
"#HackRF:",
"mossmann,dominicgs,bvernoux",
"bgamari,schneider42,miek",
"willcode,hessu,Sec42",
"yhetti,ckuethe,smunaut",
"wishi,mrbubble62,scateu..."};
"mossmann,jboone,dominicgs,",
"martinling,bvernoux,miek,",
"bgamari,schneider42,straithe,",
"grvvy,willcode,hessu,yhetti,",
"Sec42,ckuethe",
" "};
AboutView::AboutView(NavigationView& nav) {
add_children({&menu_view,
@@ -67,18 +75,18 @@ AboutView::AboutView(NavigationView& nav) {
button_ok.focus();
};
for (const std::string& authors_line : authors_list) {
for (auto& authors_line : mayhem_information_list) {
// if it's starting with #, it's a title and we have to substract the '#' and paint yellow
if (authors_line.size() > 0) {
if (authors_line[0] == '#') {
menu_view.add_item(
{authors_line.substr(1, authors_line.size() - 1),
{(std::string)authors_line.substr(1, authors_line.size() - 1),
ui::Theme::getInstance()->fg_yellow->foreground,
nullptr,
nullptr});
} else {
menu_view.add_item(
{authors_line,
{(std::string)authors_line,
Theme::getInstance()->bg_darkest->foreground,
nullptr,
nullptr});
@@ -87,10 +95,40 @@ AboutView::AboutView(NavigationView& nav) {
}
}
void AboutView::on_frame_sync() {
if (interacted) return;
if (frame_sync_count++ % ROLL_SPEED_FRAME_PER_LINE == 0) {
const auto current = menu_view.highlighted_index();
const auto count = menu_view.item_count();
if (current < count - 1) {
menu_view.set_highlighted(current + 1);
} else {
menu_view.set_highlighted(0);
}
}
}
void AboutView::focus() {
button_ok.focus();
menu_view.set_highlighted(3); // contributors block starting line
}
bool AboutView::on_touch(const TouchEvent) {
interacted = true;
return false;
}
bool AboutView::on_key(const KeyEvent) {
interacted = true;
return false;
}
bool AboutView::on_encoder(const EncoderEvent) {
interacted = true;
menu_view.focus();
// put focus on last text line to make it more obvious that list is scrollable
menu_view.set_highlighted(10);
return false;
}
} /* namespace ui */
@@ -14,6 +14,13 @@ class AboutView : public View {
std::string title() const override { return "About"; };
private:
virtual bool on_key(const KeyEvent event);
virtual bool on_encoder(const EncoderEvent event);
virtual bool on_touch(const TouchEvent event);
bool interacted{false};
uint16_t frame_sync_count{0};
void on_frame_sync();
MenuView menu_view{
{0, 0, 240, 264},
true};
@@ -22,6 +29,12 @@ class AboutView : public View {
{240 / 3, 270, 240 / 3, 24},
"OK",
};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_frame_sync();
}};
};
} // namespace ui
+36 -8
View File
@@ -94,21 +94,47 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
record_view.set_sampling_rate(24000);
options_region.on_change = [this](size_t, int32_t i) {
if (i == 0) {
field_frequency.on_change = [this](rf::Frequency f) { (void)f; };
if (i == 0) { // MAN Manual setting
field_frequency.set_value(aprs_rx_freq);
} else if (i == 1) { // NA - North America - is also the default
field_frequency.set_value(144390000);
} else if (i == 1) {
field_frequency.set_value(144800000);
} else if (i == 2) {
field_frequency.set_value(145175000);
} else if (i == 3) {
} else if (i == 2) { // NZ
field_frequency.set_value(144575000);
} else if (i == 4) {
} else if (i == 3) { // JAP
field_frequency.set_value(144640000);
} else if (i == 4) { // PHI
field_frequency.set_value(144740000);
} else if (i == 5) { // EUR
field_frequency.set_value(144800000);
} else if (i == 6) { // THA
field_frequency.set_value(144900000);
} else if (i == 7) { // AUS
field_frequency.set_value(145175000);
} else if (i == 8) { // BR
field_frequency.set_value(145570000);
} else if (i == 9) { // ISS
field_frequency.set_value(145825000);
}
options_region_id = i;
receiver_model.set_target_frequency(field_frequency.value()); // Retune
field_frequency.on_change = [this](rf::Frequency f) {
aprs_rx_freq = f;
options_region.set_selected_index(0, true);
};
};
field_frequency.set_step(100);
options_region.set_selected_index(0, true);
field_frequency.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
freq_view->on_changed = [this](rf::Frequency f) {
aprs_rx_freq = f;
field_frequency.set_value(f);
};
};
// Note: setting the region is also going to sef field_frequency.on_change
options_region.set_selected_index(options_region_id, true);
logger = std::make_unique<APRSLogger>();
if (logger)
@@ -124,6 +150,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
void APRSRxView::on_freqchg(int64_t freq) {
field_frequency.set_value(freq);
aprs_rx_freq = freq;
options_region.set_selected_index(0, true);
}
void APRSRxView::on_packet(const APRSPacketMessage* message) {
+18 -9
View File
@@ -190,6 +190,8 @@ class APRSRxView : public View {
private:
void on_data(uint32_t value, bool is_data);
bool reset_console = false;
uint8_t options_region_id = 1; // default to North America
rf::Frequency aprs_rx_freq{144390000}; // default to North America frequency
NavigationView& nav_;
RxRadioState radio_state_{
@@ -198,7 +200,10 @@ class APRSRxView : public View {
3072000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_aprs", app_settings::Mode::RX};
"rx_aprs",
app_settings::Mode::RX,
{{"options_region_id"sv, &options_region_id},
{"aprs_rx_freq"sv, &aprs_rx_freq}}};
uint8_t console_color{0};
std::string str_log{""};
@@ -220,15 +225,19 @@ class APRSRxView : public View {
OptionsField options_region{
{0 * 8, 0 * 8},
3,
{{"NA ", 0},
{"EUR", 1},
{"AUS", 2},
{"NZ ", 3},
{"ISS", 4}}};
{{"MAN", 0},
{"NA ", 1},
{"NZ ", 2},
{"JAP", 3},
{"PHI", 4},
{"EUR", 5},
{"THA", 6},
{"AUS", 7},
{"BR ", 8},
{"ISS", 9}}};
RxFrequencyField field_frequency{
{3 * 8, 0 * 16},
nav_};
FrequencyField field_frequency{
{3 * 8, 0 * 16}};
// DEBUG
RecordView record_view{
+13 -13
View File
@@ -50,10 +50,10 @@ void BattinfoView::update_result() {
text_voltage.set("UNKNOWN");
text_current.set("-");
text_charge.set("-");
text_cycles.set("-");
// text_cycles.set("-");
text_ttef.set("-");
text_method.set("-");
text_warn.set("");
// text_warn.set("");
return;
}
bool uichg = false;
@@ -96,22 +96,22 @@ void BattinfoView::update_result() {
labels_opt.hidden(true);
text_current.hidden(true);
text_charge.hidden(true);
text_cycles.hidden(true);
// text_cycles.hidden(true);
text_ttef.hidden(true);
text_warn.set("");
// text_warn.set("");
}
if ((valid_mask & battery::BatteryManagement::BATT_VALID_CYCLES) == battery::BatteryManagement::BATT_VALID_CYCLES) {
text_cycles.hidden(false);
uint16_t cycles = battery::BatteryManagement::get_cycles();
/* if ((valid_mask & battery::BatteryManagement::BATT_VALID_CYCLES) == battery::BatteryManagement::BATT_VALID_CYCLES) {
// text_cycles.hidden(false);
uint16_t cycles = 0; // battery::BatteryManagement::get_cycles();
if (cycles < 2)
text_warn.set("SoC improves after 2 cycles");
text_warn.set("SoC improves after each cycles");
else
text_warn.set("");
text_cycles.set(to_string_dec_uint(cycles));
// text_cycles.set(to_string_dec_uint(cycles));
} else {
text_cycles.hidden(true);
// text_cycles.hidden(true);
text_warn.set("");
}
} */
if ((valid_mask & battery::BatteryManagement::BATT_VALID_TTEF) == battery::BatteryManagement::BATT_VALID_TTEF) {
text_ttef.hidden(false);
float ttef = 0;
@@ -160,8 +160,8 @@ BattinfoView::BattinfoView(NavigationView& nav)
&text_method,
&button_mode,
&button_exit,
&text_cycles,
&text_warn,
// &text_cycles,
// &text_warn,
&text_ttef});
button_exit.on_select = [this, &nav](Button&) {
+4 -4
View File
@@ -63,7 +63,7 @@ class BattinfoView : public View {
{{2 * 8, 4 * 16}, "Current:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 5 * 16}, "Charge:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 6 * 16}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
// {{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "Change method:", Theme::getInstance()->fg_light->foreground},
};
@@ -85,13 +85,13 @@ class BattinfoView : public View {
Text text_ttef{
{13 * 8, 6 * 16, 10 * 16, 16},
"-"};
Text text_cycles{
/* Text text_cycles{
{13 * 8, 7 * 16, 10 * 16, 16},
"-"};
Text text_warn{
{2 * 8, 8 * 16, 30 * 8, 2 * 16},
""};
{1 * 8, 8 * 16, 30 * 8, 2 * 16},
""}; */
Button button_mode{
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
-88
View File
@@ -74,91 +74,6 @@ void DebugMemoryView::focus() {
button_done.focus();
}
/* TemperatureWidget *****************************************************/
void TemperatureWidget::paint(Painter& painter) {
const auto logger = portapack::temperature_logger;
const auto rect = screen_rect();
const Color color_background{0, 0, 64};
const Color color_foreground = Theme::getInstance()->fg_green->foreground;
const Color color_reticle{128, 128, 128};
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
const Rect graph_rect{
rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
graph_width, rect.height()};
const Rect frame_rect{
graph_rect.left() - 1, graph_rect.top() - 1,
graph_rect.width() + 2, graph_rect.height() + 2};
painter.draw_rectangle(frame_rect, color_reticle);
painter.fill_rectangle(graph_rect, color_background);
const auto history = logger.history();
for (size_t i = 0; i < history.size(); i++) {
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
const auto sample = history[i];
const auto temp = temperature(sample);
const auto y = screen_y(temp, graph_rect);
const Dim bar_height = graph_rect.bottom() - y;
painter.fill_rectangle({x, y, bar_width, bar_height}, color_foreground);
}
if (!history.empty()) {
const auto sample = history.back();
const auto temp = temperature(sample);
const auto last_y = screen_y(temp, graph_rect);
const Coord x = graph_rect.right() + 8;
const Coord y = last_y - 8;
painter.draw_string({x, y}, style(), temperature_str(temp));
}
const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
for (auto temp = display_temp_min; temp <= display_temp_max; temp += 10) {
const int32_t tick_length = 6;
const auto tick_x = graph_rect.left() - tick_length;
const auto tick_y = screen_y(temp, graph_rect);
painter.fill_rectangle({tick_x, tick_y, tick_length, 1}, color_reticle);
const auto text_x = graph_rect.left() - temp_len * 8 - 8;
const auto text_y = tick_y - 8;
painter.draw_string({text_x, text_y}, style(), temperature_str(temp));
}
}
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
// Scaling is different for MAX2837 vs MAX2839 so it's now done in the respective chip-specific module
return sensor_value;
}
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
}
Coord TemperatureWidget::screen_y(
const temperature_t temperature,
const Rect& rect) const {
int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
return y_limit;
}
/* TemperatureView *******************************************************/
TemperatureView::TemperatureView(NavigationView& nav) {
add_children({
&text_title,
&temperature_widget,
&button_done,
});
button_done.on_select = [&nav](Button&) { nav.pop(); };
}
void TemperatureView::focus() {
button_done.focus();
}
/* RegistersWidget *******************************************************/
RegistersWidget::RegistersWidget(
@@ -507,12 +422,9 @@ void DebugMenuView::on_populate() {
{"Debug Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { portapack::persistent_memory::debug_dump(); }},
{"M0 Stack Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { stack_dump(); }},
{"Memory Dump", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryDumpView>(); }},
//{"Memory Usage", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugMemoryView>(); }},
{"Peripherals", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals, [this]() { nav_.push<DebugPeripheralsMenuView>(); }},
{"Pers. Memory", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push<DebugPmemView>(); }},
//{ "Radio State", ui::Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ nav_.push<NotImplementedView>(); } },
{"SD Card", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_sdcard, [this]() { nav_.push<SDCardDebugView>(); }},
{"Temperature", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_temperature, [this]() { nav_.push<TemperatureView>(); }},
{"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push<DebugScreenTest>(); }},
{"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push<DebugReboot>(); }},
{"Ext Module", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push<ExternalModuleView>(); }},
-47
View File
@@ -86,53 +86,6 @@ class DebugMemoryView : public View {
"Done"};
};
class TemperatureWidget : public Widget {
public:
explicit TemperatureWidget(
Rect parent_rect)
: Widget{parent_rect} {
}
void paint(Painter& painter) override;
private:
using sample_t = uint32_t;
using temperature_t = int32_t;
temperature_t temperature(const sample_t sensor_value) const;
Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
std::string temperature_str(const temperature_t temperature) const;
static constexpr temperature_t display_temp_min = -10; // Accomodate negative values, present in cold startup cases
static constexpr temperature_t display_temp_scale = 3;
static constexpr int bar_width = 1;
static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
};
class TemperatureView : public View {
public:
explicit TemperatureView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Temperature"; };
private:
Text text_title{
{76, 16, 240, 16},
"Temperature",
};
TemperatureWidget temperature_widget{
{0, 40, 240, 180},
};
Button button_done{
{72, 264, 96, 24},
"Done"};
};
typedef enum {
CT_PMEM,
CT_RFFC5072,
@@ -80,7 +80,7 @@ void ExternalModuleView::on_tick_second() {
continue;
}
std::string btnText = (std::string) "App " + std::to_string(device_info->application_count) + ": " + (const char*)appInfo->app_name;
std::string btnText = (std::string) "App " + std::to_string(i + 1) + ": " + (const char*)appInfo->app_name;
switch (appInfo->menu_location) {
case app_location_t::UTILITIES:
@@ -92,6 +92,9 @@ void ExternalModuleView::on_tick_second() {
case app_location_t::TX:
btnText += " (TX)";
break;
case app_location_t::SETTINGS:
btnText += " (Settings)";
break;
case app_location_t::DEBUG:
btnText += " (Debug)";
break;
+13 -6
View File
@@ -27,7 +27,6 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_playlist.hpp"
#include "ui_remote.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_bmp_file_viewer.hpp"
#include "ui_text_editor.hpp"
@@ -704,10 +703,11 @@ bool FileManagerView::handle_file_open() {
reload_current(false);
return true;
} else if (path_iequal(rem_ext, ext)) {
}
/*else if (path_iequal(rem_ext, ext)) {
nav_.push<RemoteView>(path);
return true;
}
}*/
return false;
}
@@ -751,10 +751,17 @@ FileManagerView::FileManagerView(
text_date.set("Too many files!");
} else {
text_date.set_style(Theme::getInstance()->fg_medium);
if (selected_is_valid())
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
else
if (selected_is_valid()) {
if (get_selected_entry().path == str_back) {
text_date.set("Go page " + std::to_string(pagination + 1 - 1)); // for better explain, pagination start with 0 AKA real page - 1
} else if (get_selected_entry().path == str_next) {
text_date.set("Go page " + std::to_string(pagination + 1 + 1)); // when show this, it should display current AKA (pagination + 1) + 1 AKA next page
} else {
text_date.set((is_directory(get_selected_full_path()) ? "Created " : "Modified ") + to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
}
} else {
text_date.set("");
}
}
};
@@ -95,7 +95,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
menu_view.add_item({filename.string().substr(0, max_filename_length),
color,
&bitmap_icon_temperature,
&bitmap_icon_peripherals_details,
[this, path](KeyEvent) {
this->firmware_selected(path);
}});
@@ -177,4 +177,4 @@ void FlashUtilityView::focus() {
menu_view.focus();
}
} /* namespace ui */
} /* namespace ui */
+22 -10
View File
@@ -40,27 +40,39 @@ namespace fs = std::filesystem;
// TODO: Clean up after moving to better lookup tables.
using options_t = OptionsField::options_t;
extern options_t freqman_modulations;
extern options_t freqman_bandwidths[4];
extern options_t freqman_steps;
extern options_t freqman_steps_short;
using option_db_t = std::pair<std::string_view, int32_t>;
using options_db_t = std::vector<option_db_t>;
extern options_db_t freqman_modulations;
extern options_db_t freqman_bandwidths[4];
extern options_db_t freqman_steps;
extern options_db_t freqman_steps_short;
options_t dboptions_to_options(const options_db_t& dboptions) {
options_t options;
for (const auto& dboption : dboptions) {
options.emplace_back(dboption.first, dboption.second);
}
return options;
}
/* Set options. */
void freqman_set_modulation_option(OptionsField& option) {
option.set_options(freqman_modulations);
option.set_options(dboptions_to_options(freqman_modulations));
}
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
if (is_valid(modulation))
option.set_options(freqman_bandwidths[modulation]);
option.set_options(dboptions_to_options(freqman_bandwidths[modulation]));
}
void freqman_set_step_option(OptionsField& option) {
option.set_options(freqman_steps);
option.set_options(dboptions_to_options(freqman_steps));
}
void freqman_set_step_option_short(OptionsField& option) {
option.set_options(freqman_steps_short);
option.set_options(dboptions_to_options(freqman_steps_short));
}
namespace ui {
@@ -438,7 +450,7 @@ void FrequencyEditView::populate_bandwidth_options() {
auto& bandwidths = freqman_bandwidths[entry_.modulation];
for (auto i = 0u; i < bandwidths.size(); ++i) {
auto& item = bandwidths[i];
options.push_back({item.first, (OptionsField::value_t)i});
options.push_back({(std::string)item.first, (OptionsField::value_t)i});
}
}
@@ -451,7 +463,7 @@ void FrequencyEditView::populate_step_options() {
for (auto i = 0u; i < freqman_steps.size(); ++i) {
auto& item = freqman_steps[i];
options.push_back({item.first, (OptionsField::value_t)i});
options.push_back({(std::string)item.first, (OptionsField::value_t)i});
}
field_step.set_options(std::move(options));
+1
View File
@@ -187,6 +187,7 @@ void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
if (last_max_db != statistics.max_db) {
last_max_db = statistics.max_db;
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
rssi.set_db(statistics.max_db);
}
// refresh rssi
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
+31 -64
View File
@@ -53,6 +53,8 @@ namespace fs = std::filesystem;
#include "i2cdevmanager.hpp"
#include "i2cdev_max17055.hpp"
#include "file_reader.hpp"
extern ui::SystemView* system_view_ptr;
namespace pmem = portapack::persistent_memory;
@@ -705,14 +707,34 @@ SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
&field_encoder_dial_sensitivity,
&field_encoder_rate_multiplier,
&button_save,
&button_cancel});
&button_cancel,
&button_dial_sensitivity_plus,
&button_dial_sensitivity_minus,
&button_rate_multiplier_plus,
&button_rate_multiplier_minus,
&field_encoder_dial_direction});
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
field_encoder_dial_direction.set_by_value(pmem::encoder_dial_direction());
button_dial_sensitivity_plus.on_select = [this](Button&) {
field_encoder_dial_sensitivity.on_encoder(1);
};
button_dial_sensitivity_minus.on_select = [this](Button&) {
field_encoder_dial_sensitivity.on_encoder(-1);
};
button_rate_multiplier_plus.on_select = [this](Button&) {
field_encoder_rate_multiplier.on_encoder(1);
};
button_rate_multiplier_minus.on_select = [this](Button&) {
field_encoder_rate_multiplier.on_encoder(-1);
};
button_save.on_select = [&nav, this](Button&) {
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
pmem::set_encoder_dial_direction(field_encoder_dial_direction.selected_index_value());
nav.pop();
};
@@ -801,6 +823,7 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
send_system_refresh();
nav.pop();
};
// only enable invert OR fake brightness
checkbox_invert_switch.on_select = [this](Checkbox&, bool v) {
if (v) checkbox_brightness_switch.set_value(false);
@@ -974,67 +997,6 @@ void SetMenuColorView::focus() {
button_save.focus();
}
/* SetAutoStartView ************************************/
SetAutostartView::SetAutostartView(NavigationView& nav) {
add_children({&labels,
&button_save,
&button_cancel,
&button_reset,
&options});
button_save.on_select = [&nav, this](Button&) {
autostart_app = "";
if (selected != 0) {
auto it = full_app_list.find(selected);
if (it != full_app_list.end())
autostart_app = it->second;
}
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
button_reset.on_select = [this](Button&) {
selected = 0;
options.set_selected_index(0);
autostart_app = "";
};
// options
i = 0;
OptionsField::option_t o{"-none-", i};
opts.emplace_back(o);
for (auto& app : NavigationView::appList) {
if (app.id == nullptr) continue;
i++;
o = {app.displayName, i};
opts.emplace_back(o);
full_app_list.emplace(i, app.id);
if (autostart_app == app.id) selected = i;
}
ExternalItemsMenuLoader::load_all_external_items_callback([this](ui::AppInfoConsole& app) {
if (app.appCallName == nullptr) return;
i++;
OptionsField::option_t o = {app.appFriendlyName, i};
opts.emplace_back(o);
full_app_list.emplace(i, app.appCallName);
if (autostart_app == app.appCallName) selected = i;
});
options.set_options(opts);
options.on_change = [this](size_t, OptionsField::value_t v) {
selected = v;
};
options.set_selected_index(selected);
}
void SetAutostartView::focus() {
options.focus();
}
/* SetThemeView ************************************/
SetThemeView::SetThemeView(NavigationView& nav) {
@@ -1116,9 +1078,12 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav)
}
void SettingsMenuView::on_populate() {
if (pmem::show_gui_return_icon()) {
const bool return_icon = pmem::show_gui_return_icon();
if (return_icon) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [this]() { nav_.pop(); }}});
}
add_items({
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [this]() { nav_.push<AppSettingsView>(); }},
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [this]() { nav_.push<SetAudioView>(); }},
@@ -1137,9 +1102,11 @@ void SettingsMenuView::on_populate() {
{"Display", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetDisplayView>(); }},
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
{"Theme", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetThemeView>(); }},
{"Autostart", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetAutostartView>(); }},
});
if (battery::BatteryManagement::isDetected()) add_item({"Battery", ui::Color::dark_cyan(), &bitmap_icon_batt_icon, [this]() { nav_.push<SetBatteryView>(); }});
add_external_items(nav_, app_location_t::SETTINGS, *this, return_icon ? 1 : 0);
}
} /* namespace ui */
+34 -52
View File
@@ -574,6 +574,7 @@ class SetQRCodeView : public View {
};
};
using portapack::persistent_memory::encoder_dial_direction;
using portapack::persistent_memory::encoder_dial_sensitivity;
using portapack::persistent_memory::encoder_rate_multiplier;
@@ -587,30 +588,52 @@ class SetEncoderDialView : public View {
private:
Labels labels{
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "rotation position (number of", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 3 * 16}, "steps per full rotation):", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 5 * 16}, "Dial sensitivity:", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 9 * 16}, "rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 10 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 12 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
{{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},
{{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},
{{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
};
OptionsField field_encoder_dial_sensitivity{
{20 * 8, 5 * 16},
{20 * 8, 3 * 16},
6,
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
NumberField field_encoder_rate_multiplier{
{20 * 8, 12 * 16},
{20 * 8, 10 * 16},
2,
{1, 15},
1,
' '};
OptionsField field_encoder_dial_direction{
{18 * 8, 14 * 16},
7,
{{"NORMAL", false},
{"REVERSE", true}}};
Button button_dial_sensitivity_plus{
{20 * 8, 2 * 16, 16, 16},
"+"};
Button button_dial_sensitivity_minus{
{20 * 8, 4 * 16, 16, 16},
"-"};
Button button_rate_multiplier_plus{
{20 * 8, 9 * 16, 16, 16},
"+"};
Button button_rate_multiplier_minus{
{20 * 8, 11 * 16, 16, 16},
"-"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
@@ -709,7 +732,6 @@ class SetConfigModeView : public View {
"Cancel",
};
};
using portapack::persistent_memory::fake_brightness_level_options;
class SetDisplayView : public View {
@@ -889,47 +911,6 @@ class SetMenuColorView : public View {
};
};
class SetAutostartView : public View {
public:
SetAutostartView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Autostart"; };
private:
int32_t i = 0;
std::string autostart_app{""};
OptionsField::options_t opts{};
std::map<int32_t, std::string> full_app_list{}; // looking table
int32_t selected = 0;
SettingsStore nav_setting{
"nav"sv,
{{"autostart_app"sv, &autostart_app}}};
Labels labels{
{{1 * 8, 1 * 16}, "Select app to start on boot", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 2 * 16}, "(an SD Card is required)", 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,
{},
true};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
Button button_reset{
{2 * 8, 6 * 16, screen_width - 4 * 8, 32},
"Reset"};
};
class SetThemeView : public View {
public:
SetThemeView(NavigationView& nav);
@@ -1013,6 +994,7 @@ class SettingsMenuView : public BtnGridView {
private:
NavigationView& nav_;
void on_populate() override;
};
+1 -1
View File
@@ -56,7 +56,7 @@ SIGFRXView::~SIGFRXView() {
void SIGFRXView::paint(Painter& painter) {
uint8_t i, xp;
// portapack::display.drawBMP({0, 302-160}, fox_bmp);
// 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);
for (i = 0; i < 6; i++) {
xp = sigfrx_marks[i * 3];
+33 -6
View File
@@ -44,9 +44,9 @@ SigGenView::~SigGenView() {
void SigGenView::update_config() {
if (checkbox_stop.value())
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), field_stop.value());
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), field_stop.value());
else
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), 0);
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), 0);
}
void SigGenView::update_tone() {
@@ -78,6 +78,7 @@ SigGenView::SigGenView(
baseband::run_image(portapack::spi_flash::image_tag_siggen);
add_children({&labels,
&options_mod,
&options_shape,
&text_shape,
&symfield_tone,
@@ -87,22 +88,48 @@ SigGenView::SigGenView(
&field_stop,
&tx_view});
symfield_tone.hidden(1); // At first launch , by default we are in CW Shape has NO MOD , we are not using Tone modulation.
symfield_tone.hidden(true); // At first launch , by default we are in CW: Shape ignored, we are not using Tone modulation.
options_shape.hidden(true);
text_shape.hidden(true);
symfield_tone.set_value(1000); // Default: 1000 Hz
options_shape.on_change = [this](size_t, OptionsField::value_t v) {
text_shape.set(shape_strings[v]);
if (auto_update)
update_config();
if ((v == 0) || (v == 6)) { // In Shapes Options (CW & Pseudo Random Noise) we are not using Tone modulation freq.
symfield_tone.hidden(1);
if (v == 5) { // In Shape Pseudo Random Noise we are not using Tone modulation freq.
symfield_tone.hidden(true);
} else {
symfield_tone.hidden(0);
symfield_tone.hidden(false);
}
set_dirty();
};
options_shape.set_selected_index(0);
text_shape.set(shape_strings[0]);
options_mod.on_change = [this](size_t, OptionsField::value_t v) {
if (auto_update)
update_config();
if (v == 0) { // In Modulation Options CW we are not using Tone modulation freq.
symfield_tone.hidden(true);
} else {
symfield_tone.hidden(false);
}
if ((v == 0) || (v == 2) || (v == 3)) { // In Modulation Options CW, QPSK, BPSK we are not using Shapes.
options_shape.hidden(true);
text_shape.hidden(true);
} else {
options_shape.hidden(false);
text_shape.hidden(false);
}
set_dirty();
};
options_mod.set_selected_index(0);
field_stop.set_value(1);
symfield_tone.set_value(1000); // Default: 1000 Hz
+35 -30
View File
@@ -58,68 +58,73 @@ class SigGenView : public View {
app_settings::SettingsManager settings_{
"tx_siggen", app_settings::Mode::TX};
const std::string shape_strings[9] = {
"CW (No mod.) ",
"Sine mod. FM",
"Triangle mod.FM", // max 15 character text space.
"Saw up mod. FM",
"Saw down mod.FM",
"Square mod. FM",
"Pseudo Noise FM", // using 16 bits LFSR register, 16 order polynomial feedback.
"BPSK 0,1,0,1...",
"QPSK 00-01-10.."};
const std::string shape_strings[6] = {// max 15 character text space.
"Sine",
"Triangle",
"Saw up",
"Saw down",
"Square",
"Pseudo Noise"};
bool auto_update{false};
Labels labels{
{{3 * 8, 4 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
{{22 * 8, 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground},
{{8 * 8, 20 * 8}, "Modulation: FM", Theme::getInstance()->fg_light->foreground}};
{{3 * 8, 2 * 8}, "Modulation:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 3 * 8 + 8 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 2 * 8 + 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
{{22 * 8, 2 * 8 + 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground}};
ImageOptionsField options_shape{
{10 * 8, 4, 32, 32},
{10 * 8, 3 * 8 + 8, 32, 32},
Theme::getInstance()->bg_darkest->foreground,
Theme::getInstance()->bg_darkest->background,
{{&bitmap_sig_cw, 0},
{&bitmap_sig_sine, 1},
{&bitmap_sig_tri, 2},
{&bitmap_sig_saw_up, 3},
{&bitmap_sig_saw_down, 4},
{&bitmap_sig_square, 5},
{&bitmap_sig_noise, 6},
{&bitmap_sig_noise, 7}, // Pending to add a correct BPSK icon.
{&bitmap_sig_noise, 8}}}; // Pending to add a correct QPSK icon.
{{&bitmap_sig_sine, 0},
{&bitmap_sig_tri, 1},
{&bitmap_sig_saw_up, 2},
{&bitmap_sig_saw_down, 3},
{&bitmap_sig_square, 4},
{&bitmap_sig_noise, 5}}};
Text text_shape{
{15 * 8, 4 + 10, 15 * 8, 16},
{15 * 8, 3 * 8 + 8 + 10, 15 * 8, 16},
""};
SymField symfield_tone{
{12 * 8, 7 * 8},
{12 * 8, 2 * 8 + 7 * 8},
5};
Button button_update{
{5 * 8, 10 * 8, 8 * 8, 3 * 8},
{5 * 8, 2 * 8 + 10 * 8, 8 * 8, 3 * 8},
"Update"};
Checkbox checkbox_auto{
{15 * 8, 10 * 8},
{15 * 8, 2 * 8 + 10 * 8},
4,
"Auto"};
Checkbox checkbox_stop{
{5 * 8, 15 * 8},
{5 * 8, 2 * 8 + 15 * 8},
10,
"Stop after"};
NumberField field_stop{
{20 * 8, 15 * 8 + 4},
{20 * 8, 2 * 8 + 15 * 8 + 4},
2,
{1, 99},
1,
' '};
OptionsField options_mod{
{15 * 8, 2 * 8},
12,
{{"CW (No mod.)", 0},
{"FM", 1},
{"BPSK", 2},
{"QPSK", 3},
{"DSB", 4},
{"AM 100% dep.", 5},
{"AM 50% depth", 6}}};
TransmitterView tx_view{
16 * 16,
10000,
+1 -1
View File
@@ -119,7 +119,7 @@ bool SplashViewer::on_key(const KeyEvent key) {
void SplashViewer::paint(Painter& painter) {
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
if (!portapack::display.drawBMP2({0, 0}, path_)) {
if (!portapack::display.draw_bmp_from_sdcard_file({0, 0}, path_)) {
painter.draw_string({10, 160}, *Theme::getInstance()->bg_darkest, "Not a valid splash image.");
return;
}
+26 -1
View File
@@ -24,6 +24,7 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
@@ -31,6 +32,18 @@ using namespace ui;
namespace ui {
std::string SubGhzDRecentEntry::to_csv() {
std::string csv = ";";
csv += SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)sensorType);
csv += ";" + to_string_dec_uint(bits) + ";";
csv += to_string_hex(data, 64 / 4);
return csv;
}
void SubGhzDLogger::log_data(SubGhzDRecentEntry& data) {
log_file.write_entry(data.to_csv());
}
void SubGhzDRecentEntryDetailView::update_data() {
// process protocol data
parseProtocol();
@@ -76,16 +89,25 @@ SubGhzDView::SubGhzDView(NavigationView& nav)
&field_vga,
&field_frequency,
&button_clear_list,
&check_log,
&recent_entries_view});
baseband::run_image(portapack::spi_flash::image_tag_subghzd);
logger = std::make_unique<SubGhzDLogger>();
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_step(10000);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
if (logger && logging) {
logger->append(logs_dir.string() + "/SUBGHZDLOG_" + to_string_timestamp(rtc_time::now()) + ".CSV");
logger->write_header();
}
};
check_log.set_value(logging);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const SubGhzDRecentEntry& entry) {
@@ -107,6 +129,9 @@ void SubGhzDView::on_tick_second() {
void SubGhzDView::on_data(const SubGhzDDataMessage* data) {
SubGhzDRecentEntry key{data->sensorType, data->data, data->bits};
if (logger && logging) {
logger->log_data(key);
}
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
// Found within. Move to front of list, increment counter.
+30 -1
View File
@@ -34,6 +34,7 @@
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
#include "log_file.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/subghztypes.hpp"
@@ -68,6 +69,23 @@ struct SubGhzDRecentEntry {
void reset_age() {
age = 0;
}
std::string to_csv();
};
class SubGhzDLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_data(SubGhzDRecentEntry& data);
void write_header() {
log_file.write_entry(";Type; Bits; Data;");
}
private:
LogFile log_file{};
};
using SubGhzDRecentEntries = RecentEntries<SubGhzDRecentEntry>;
using SubGhzDRecentEntriesView = RecentEntriesView<SubGhzDRecentEntries>;
@@ -93,10 +111,13 @@ class SubGhzDView : public View {
1'750'000 /* bandwidth */,
4'000'000 /* sampling rate */,
ReceiverModel::Mode::AMAudio};
bool logging = false;
app_settings::SettingsManager settings_{
"rx_subghzd",
app_settings::Mode::RX,
{}};
{
{"log"sv, &logging},
}};
SubGhzDRecentEntries recent{};
@@ -118,8 +139,16 @@ class SubGhzDView : public View {
{0, 16, 7 * 8, 32},
"Clear"};
Checkbox check_log{
{10 * 8, 18},
3,
"Log",
true};
static constexpr auto header_height = 3 * 16;
std::unique_ptr<SubGhzDLogger> logger{};
const RecentEntriesColumns columns{{
{"Type", 19},
{"Bits", 4},
@@ -25,6 +25,7 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "portapack_persistent_memory.hpp"
#include "../baseband/fprotos/fprotogeneral.hpp"
@@ -35,6 +36,21 @@ namespace pmem = portapack::persistent_memory;
namespace ui {
std::string WeatherRecentEntry::to_csv() {
std::string csv = ";";
csv += WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)sensorType);
csv += ";" + to_string_dec_uint(id) + ";";
csv += to_string_decimal(temp, 2) + ";";
csv += to_string_dec_uint(humidity) + ";";
csv += to_string_dec_uint(channel) + ";";
csv += to_string_dec_uint(battery_low);
return csv;
}
void WeatherLogger::log_data(WeatherRecentEntry& data) {
log_file.write_entry(data.to_csv());
}
void WeatherRecentEntryDetailView::update_data() {
// set text elements
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType));
@@ -98,8 +114,11 @@ WeatherView::WeatherView(NavigationView& nav)
&field_frequency,
&options_temperature,
&button_clear_list,
&check_log,
&recent_entries_view});
logger = std::make_unique<WeatherLogger>();
baseband::run_image(portapack::spi_flash::image_tag_weather);
button_clear_list.on_select = [this](Button&) {
@@ -114,6 +133,15 @@ WeatherView::WeatherView(NavigationView& nav)
};
options_temperature.set_selected_index(weather_units_fahr, false);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
if (logger && logging) {
logger->append(logs_dir.string() + "/WEATHERLOG_" + to_string_timestamp(rtc_time::now()) + ".CSV");
logger->write_header();
}
};
check_log.set_value(logging);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) {
@@ -140,6 +168,9 @@ void WeatherView::on_tick_second() {
void WeatherView::on_data(const WeatherDataMessage* data) {
WeatherRecentEntry key = process_data(data);
if (logger && logging) {
logger->log_data(key);
}
// WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low};
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
@@ -30,6 +30,7 @@
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
#include "log_file.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/weathertypes.hpp"
@@ -78,7 +79,25 @@ struct WeatherRecentEntry {
void reset_age() {
age = 0;
}
std::string to_csv();
};
class WeatherLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_data(WeatherRecentEntry& data);
void write_header() {
log_file.write_entry(";Type; id; Temp; Hum; CH; Batt");
}
private:
LogFile log_file{};
};
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>;
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>;
@@ -103,11 +122,13 @@ class WeatherView : public View {
1'750'000 /* bandwidth */,
2'000'000 /* sampling rate */,
ReceiverModel::Mode::AMAudio};
bool logging = false;
app_settings::SettingsManager settings_{
"rx_weather",
app_settings::Mode::RX,
{
{"units_fahr"sv, &weather_units_fahr},
{"log"sv, &logging},
}};
WeatherRecentEntries recent{};
@@ -140,8 +161,16 @@ class WeatherView : public View {
{0, 16, 7 * 8, 32},
"Clear"};
Checkbox check_log{
{10 * 8, 18},
3,
"Log",
true};
static constexpr auto header_height = 3 * 16;
std::unique_ptr<WeatherLogger> logger{};
const RecentEntriesColumns columns{{
{"Type", 10},
{"Temp", 5},
File diff suppressed because it is too large Load Diff
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_acars_rx.application_information"), us
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_acars */ {'P', 'A', 'C', 'A'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_adsbtx.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_adsbtx */ {'P', 'A', 'D', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_afsk_rx.application_information"), use
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_analogtv.application_information"), us
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_am_tv */ {'P', 'A', 'M', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_whipcalc.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::antenna_length {
void initialize_app(ui::NavigationView& nav) {
nav.push<WhipCalcView>();
}
} // namespace ui::external_app::antenna_length
extern "C" {
__attribute__((section(".external_app.app_antenna_length.application_information"), used)) application_information_t _application_information_antenna_length = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::antenna_length::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Antenna Length",
/*.bitmap_data = */ {0x38, 0x3E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x10, 0x2E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x26, 0x38, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x3E, 0x00, 0x00},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -32,8 +32,9 @@
#include <cstring>
using namespace portapack;
using namespace ui;
namespace ui {
namespace ui::external_app::antenna_length {
void WhipCalcView::focus() {
field_frequency.focus();
@@ -176,4 +177,4 @@ void WhipCalcView::load_antenna_db() {
void WhipCalcView::add_default_antenna() {
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
}
} // namespace ui
} // namespace ui::external_app::antenna_length
@@ -31,7 +31,10 @@
#include "string_format.hpp"
#include <vector>
namespace ui {
using namespace ui;
namespace ui::external_app::antenna_length {
class WhipCalcView : public View {
public:
WhipCalcView(NavigationView& nav);
@@ -90,6 +93,6 @@ class WhipCalcView : public View {
"Back"};
};
} /* namespace ui */
} // namespace ui::external_app::antenna_length
#endif /*__UI_WHIPCALC__*/
+84
View File
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_app_manager.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::app_manager {
void initialize_app(ui::NavigationView& nav) {
nav.push<AppManagerView>();
}
} // namespace ui::external_app::app_manager
extern "C" {
__attribute__((section(".external_app.app_app_manager.application_information"), used)) application_information_t _application_information_app_manager = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::app_manager::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "AppManager",
/*.bitmap_data = */ {
0xE0,
0x00,
0x10,
0x01,
0x10,
0x01,
0x1F,
0x1F,
0x01,
0x10,
0x01,
0x10,
0x01,
0x10,
0x01,
0x70,
0x01,
0x80,
0x01,
0x80,
0x01,
0x80,
0x01,
0x70,
0xE1,
0x10,
0x11,
0x11,
0x11,
0x11,
0x1F,
0x1F,
},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::SETTINGS,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,275 @@
/*
* copyleft spammingdramaqueen
*
* 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_app_manager.hpp"
#include "ui_navigation.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "file.hpp"
namespace fs = std::filesystem;
#include "string_format.hpp"
#include "file_reader.hpp"
using namespace portapack;
namespace ui::external_app::app_manager {
/* AppManagerView **************************************/
/* | inner apps | external apps |
* ------------------------------
* | id | appCallName |
* | displayName|appFriendlyName|
*/
AppManagerView::AppManagerView(NavigationView& nav)
: nav_{nav} {
add_children({&labels,
&menu_view,
&text_app_info,
&button_hide_unhide,
&button_clean_hide,
&button_set_cancel_autostart,
&button_clean_autostart});
menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
menu_view.on_highlight = [this]() {
if (menu_view.highlighted_index() >= app_list_index) {
text_app_info.set("");
return;
}
std::string info;
auto app_name = get_app_info(menu_view.highlighted_index(), true);
auto app_id = get_app_info(menu_view.highlighted_index(), false);
info += "Hidden:";
if (is_blacklisted(app_name)) {
info += "Yes ";
} else {
info += "No ";
}
info += "Autostart:";
if (is_autostart_app(app_id)) {
info += "Yes";
} else {
info += "No";
}
if (info.empty()) {
info = "Highlight an app";
}
text_app_info.set(info);
};
button_hide_unhide.on_select = [this](Button&) {
hide_unhide_app();
refresh_list();
};
button_clean_hide.on_select = [this](Button&) {
clean_blacklist();
refresh_list();
};
button_set_cancel_autostart.on_select = [this](Button&) {
set_unset_autostart_app();
refresh_list();
};
button_clean_autostart.on_select = [this](Button&) {
unset_auto_start();
refresh_list();
};
refresh_list();
}
void AppManagerView::refresh_list() {
auto previous_cursor_index = menu_view.highlighted_index();
app_list_index = 0;
menu_view.clear();
size_t index = 0;
auto padding = [](const std::string& str) {
return str.length() < 25 ? std::string(25 - str.length(), ' ') + '' : "";
// that weird char is a icon in portapack's font base that looks like a switch, which i use to indicate the auto start
};
for (auto& app : NavigationView::appList) {
if (app.id == nullptr) continue;
app_list_index++;
menu_view.add_item({app.displayName + std::string(is_autostart_app(app.id) ? padding(app.displayName) : ""),
app.iconColor,
app.icon,
[this, app_id = std::string(app.id)](KeyEvent) {
button_hide_unhide.focus();
}});
}
ExternalItemsMenuLoader::load_all_external_items_callback([this, &index, &padding](ui::AppInfoConsole& app) {
if (app.appCallName == nullptr) return;
app_list_index++;
menu_view.add_item({app.appFriendlyName + std::string(is_autostart_app(app.appCallName) ? padding(app.appFriendlyName) : ""),
ui::Color::light_grey(),
&bitmap_icon_sdcard,
[this, app_id = std::string(app.appCallName)](KeyEvent) {
button_hide_unhide.focus();
}});
});
menu_view.set_highlighted(previous_cursor_index);
}
void AppManagerView::focus() {
menu_view.focus();
}
void AppManagerView::hide_app() {
std::vector<std::string> blacklist;
get_blacklist(blacklist);
blacklist.push_back(get_app_info(menu_view.highlighted_index(), true));
write_blacklist(blacklist);
}
void AppManagerView::unhide_app() {
std::vector<std::string> blacklist;
get_blacklist(blacklist);
blacklist.erase(std::remove(blacklist.begin(), blacklist.end(), get_app_info(menu_view.highlighted_index(), true)), blacklist.end());
write_blacklist(blacklist);
}
void AppManagerView::hide_unhide_app() {
if (is_blacklisted(get_app_info(menu_view.highlighted_index(), true)))
unhide_app();
else
hide_app();
}
void AppManagerView::get_blacklist(std::vector<std::string>& blacklist) {
File f;
auto error = f.open(u"SETTINGS/blacklist");
if (error)
return;
auto reader = FileLineReader(f);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue;
blacklist.push_back(trim(line));
}
}
void AppManagerView::write_blacklist(const std::vector<std::string>& blacklist) {
File f;
auto error = f.create(u"SETTINGS/blacklist");
if (error)
return;
for (const auto& app_name : blacklist) {
auto line = app_name + "\n";
f.write(line.c_str(), line.length());
}
}
void AppManagerView::clean_blacklist() {
std::vector<std::string> blacklist = {};
write_blacklist(blacklist);
}
bool AppManagerView::is_blacklisted(const std::string& app_name) {
std::vector<std::string> blacklist;
get_blacklist(blacklist);
return std::find(blacklist.begin(), blacklist.end(), app_name) != blacklist.end();
}
void AppManagerView::set_auto_start() {
auto app_index = menu_view.highlighted_index();
if (app_index >= app_list_index) return;
auto id_aka_app_call_name = get_app_info(app_index, false);
autostart_app = id_aka_app_call_name;
refresh_list();
}
void AppManagerView::unset_auto_start() {
autostart_app = "";
refresh_list();
}
void AppManagerView::set_unset_autostart_app() {
auto app_index = menu_view.highlighted_index();
if (app_index >= app_list_index) return;
auto id_aka_friendly_name = get_app_info(app_index, false);
if (is_autostart_app(id_aka_friendly_name))
unset_auto_start();
else
set_auto_start();
}
bool AppManagerView::is_autostart_app(const char* id_aka_friendly_name) {
return autostart_app == std::string(id_aka_friendly_name);
}
bool AppManagerView::is_autostart_app(const std::string& id_aka_friendly_name) {
return autostart_app == id_aka_friendly_name;
}
std::string AppManagerView::get_app_info(uint16_t index, bool is_display_name) {
size_t current_index = 0;
std::string result;
for (auto& app : NavigationView::appList) {
if (app.id == nullptr) continue;
if (current_index == index) {
return is_display_name ? std::string(app.displayName) : std::string(app.id);
}
current_index++;
}
ExternalItemsMenuLoader::load_all_external_items_callback([&current_index, &index, &result, &is_display_name](ui::AppInfoConsole& app) {
if (app.appCallName == nullptr) return;
if (current_index == index) {
result = is_display_name ? app.appFriendlyName : app.appCallName;
}
current_index++;
});
return result;
}
} // namespace ui::external_app::app_manager
@@ -0,0 +1,87 @@
/*
* copyleft spammingdramaqueen
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_APP_MANAGER_H__
#define __UI_APP_MANAGER_H__
#include "ui_navigation.hpp"
namespace ui::external_app::app_manager {
class AppManagerView : public View {
public:
AppManagerView(NavigationView& nav);
std::string title() const override { return "AppMan"; };
private:
NavigationView& nav_;
std::string autostart_app{""};
SettingsStore nav_setting{
"nav"sv,
{{"autostart_app"sv, &autostart_app}}};
void focus() override;
void refresh_list();
uint16_t app_list_index{0};
Labels labels{
{{0 * 8, 0 * 16}, "App list:", Theme::getInstance()->fg_light->foreground}};
MenuView menu_view{};
Text text_app_info{
{0, 27 * 8, screen_width, 16},
"Highlight an app"};
Button button_hide_unhide{
{0, 29 * 8, screen_width / 2 - 1, 32},
"Hide/Show"};
Button button_clean_hide{
{screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
"Clean Hidden"};
Button button_set_cancel_autostart{
{0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
"Set Autostart"};
Button button_clean_autostart{
{screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
"Del Autostart"};
std::string get_app_info(uint16_t index, bool is_display_name);
void get_blacklist(std::vector<std::string>& blacklist);
void write_blacklist(const std::vector<std::string>& blacklist);
bool is_blacklisted(const std::string& app_display_name);
void hide_app();
void unhide_app();
void hide_unhide_app();
void clean_blacklist();
bool is_autostart_app(const std::string& display_name);
void set_auto_start();
void unset_auto_start();
void set_unset_autostart_app();
bool is_autostart_app(const char* id_aka_app_call_name);
};
} // namespace ui::external_app::app_manager
#endif // __UI_APP_MANAGER_H__
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_audio_test.application_information"),
},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::DEBUG,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'B', 'P'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+2 -2
View File
@@ -26,7 +26,7 @@
using namespace portapack;
namespace ui {
namespace ui::external_app::audio_test {
AudioTestView::AudioTestView(NavigationView& nav)
: nav_{nav} {
@@ -103,4 +103,4 @@ void AudioTestView::update_audio_beep() {
baseband::request_beep_stop();
}
} /* namespace ui */
} /* namespace ui::external_app::audio_test */
+2 -2
View File
@@ -25,7 +25,7 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
namespace ui {
namespace ui::external_app::audio_test {
class AudioTestView : public View {
public:
@@ -96,6 +96,6 @@ class AudioTestView : public View {
Theme::getInstance()->bg_dark->background};
};
} /* namespace ui */
} /* namespace ui::external_app::audio_test */
#endif /*__UI_AUDIO_TEST_H__*/
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_blespam.application_information"), use
},
/*.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
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_calculator.application_information"),
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -76,6 +76,7 @@ __attribute__((section(".external_app.app_coasterp.application_information"), us
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+310
View File
@@ -0,0 +1,310 @@
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
// If you can read this, you're a nerd. :P
// Come join us at https://discord.gg/thepiratesreborn
#include "cvs_spam.hpp"
#include "io_file.hpp"
#include "metadata_file.hpp"
#include "oversample.hpp"
#include "io_convert.hpp"
#include "lfsr_random.hpp"
using namespace portapack;
namespace ui::external_app::cvs_spam {
void CVSSpamView::file_error(const std::filesystem::path& path, const std::string& error_details) {
nav_.display_modal("Error",
"File error:\n" + path.string() + "\n" + error_details);
}
void CVSSpamView::refresh_list() {
file_list.clear();
current_page = page;
uint32_t count = 0;
for (const auto& entry : std::filesystem::directory_iterator(cvsfiles_dir, u"*")) {
if (std::filesystem::is_regular_file(entry.status())) {
auto filename = entry.path().filename().string();
auto extension = entry.path().extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
if (extension == ".c16" || extension == ".cu8") {
if (count >= (page - 1) * 50 && count < page * 50) {
file_list.push_back(entry.path());
if (file_list.size() == 50) {
page++;
break;
}
}
count++;
}
}
}
if (file_list.empty()) {
if (page == 1) {
menu_view.hidden(true);
text_empty.hidden(false);
} else {
page = 1;
refresh_list();
return;
}
} else {
menu_view.hidden(false);
text_empty.hidden(true);
menu_view.clear();
for (const auto& file : file_list) {
menu_view.add_item({file.filename().string(),
ui::Theme::getInstance()->fg_green->foreground,
nullptr,
[this](KeyEvent key) {
if (key == KeyEvent::Select) {
start_tx(menu_view.highlighted_index());
}
}});
}
page_info.set("Page " + std::to_string(current_page));
menu_view.set_highlighted(0);
}
if (file_list.size() < 50) {
page = 1;
}
}
void CVSSpamView::start_tx(const uint32_t id) {
if (is_active()) {
stop_tx();
return;
}
const uint32_t sample_rate = 250000;
current_file = cvsfiles_dir / file_list[id].filename();
File capture_file;
auto open_error = capture_file.open(current_file);
if (open_error) {
file_error(current_file,
"Cannot open file.\n"
"Initial file check failed.\n"
"Path: " +
cvsfiles_dir.string() +
"\n"
"Error: " +
std::to_string(static_cast<uint32_t>(open_error)));
return;
}
auto metadata_path = get_metadata_path(current_file);
auto metadata = read_metadata_file(metadata_path);
if (!metadata) {
metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
}
auto file_size = capture_file.size();
capture_file.close();
replay_thread.reset();
transmitter_model.disable();
ready_signal = false;
baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
auto reader = std::make_unique<FileConvertReader>();
if (auto error = reader->open(current_file)) {
file_error(current_file,
"Cannot read C16 data.\n"
"Check file format/perms.\n"
"Rate: " +
to_string_dec_uint(metadata->sample_rate) +
"\n"
"Size: " +
to_string_dec_uint(file_size) +
"\n"
"Error: " +
std::to_string(static_cast<uint32_t>(error)));
return;
}
progressbar.set_value(0);
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
transmitter_model.set_target_frequency(metadata->center_frequency);
transmitter_model.enable();
chThdSleepMilliseconds(100);
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
read_size,
buffer_count,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
}
void CVSSpamView::start_random_tx() {
if (is_active()) {
stop_tx();
return;
}
if (file_list.empty()) {
nav_.display_modal("Error", "No files found!");
return;
}
lfsr_v = lfsr_iterate(lfsr_v);
size_t random_index = lfsr_v % file_list.size();
const uint32_t sample_rate = 250000;
current_file = cvsfiles_dir / file_list[random_index].filename();
File capture_file;
auto open_error = capture_file.open(current_file);
if (open_error) {
file_error(current_file, "Cannot open file.\nInitial check failed.");
return;
}
auto metadata_path = get_metadata_path(current_file);
auto metadata = read_metadata_file(metadata_path);
if (!metadata) {
metadata = capture_metadata{transmitter_model.target_frequency(), sample_rate};
}
capture_file.close();
replay_thread.reset();
transmitter_model.disable();
ready_signal = false;
baseband::set_sample_rate(metadata->sample_rate, get_oversample_rate(metadata->sample_rate));
auto reader = std::make_unique<FileConvertReader>();
if (auto error = reader->open(current_file)) {
file_error(current_file, "Cannot read file data.");
return;
}
progressbar.set_value(0);
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500000 ? 1750000 : 2500000);
transmitter_model.set_target_frequency(metadata->center_frequency);
transmitter_model.enable();
chThdSleepMilliseconds(100);
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
read_size,
buffer_count,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
}
void CVSSpamView::on_tx_progress(const uint32_t progress) {
if (is_active()) {
progressbar.set_value(progress % 100);
progressbar.set_style(Theme::getInstance()->fg_red);
}
}
void CVSSpamView::focus() {
menu_view.focus();
}
bool CVSSpamView::is_active() const {
return (bool)replay_thread;
}
void CVSSpamView::stop_tx() {
replay_thread.reset();
transmitter_model.disable();
ready_signal = false;
thread_sync_complete = false;
chaos_mode = false;
progressbar.set_value(0);
chThdSleepMilliseconds(50);
}
CVSSpamView::CVSSpamView(NavigationView& nav)
: nav_{nav},
current_file{} {
baseband::run_image(portapack::spi_flash::image_tag_replay);
add_children({&menu_view,
&text_empty,
&button_prev_page,
&button_next_page,
&page_info,
&button_send,
&button_chaos,
&button_stop,
&progressbar});
button_send.set_style(Theme::getInstance()->fg_magenta);
button_chaos.set_style(Theme::getInstance()->fg_yellow);
button_stop.set_style(Theme::getInstance()->fg_red);
transmitter_model.set_sampling_rate(1536000);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.set_rf_amp(true);
transmitter_model.set_tx_gain(47);
transmitter_model.set_channel_bandwidth(1750000);
refresh_list();
button_prev_page.on_select = [this](Button&) {
if (is_active()) return;
if (current_page == 1) return;
if (current_page == 2) page = 1;
page = current_page - 1;
refresh_list();
};
button_next_page.on_select = [this](Button&) {
if (is_active()) return;
refresh_list();
};
button_send.on_select = [this](Button&) {
if (!file_list.empty()) {
start_tx(menu_view.highlighted_index());
}
};
button_chaos.on_select = [this](Button&) {
if (is_active()) {
chaos_mode = false;
stop_tx();
} else {
chaos_mode = true;
start_random_tx();
}
};
button_stop.on_select = [this](Button&) {
if (is_active()) {
stop_tx();
}
};
}
CVSSpamView::~CVSSpamView() {
stop_tx();
baseband::shutdown();
}
} // namespace ui::external_app::cvs_spam
+136
View File
@@ -0,0 +1,136 @@
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
// If you can read this, you're a nerd. :P
// Come join us at https://discord.gg/thepiratesreborn
#pragma once
#include "ui_widget.hpp"
#include "ui_transmitter.hpp"
#include "replay_thread.hpp"
#include "baseband_api.hpp"
#include "ui_language.hpp"
#include "file_path.hpp"
using namespace portapack;
namespace ui::external_app::cvs_spam {
class CVSSpamView : public View {
public:
explicit CVSSpamView(NavigationView& nav);
~CVSSpamView();
void focus() override;
std::string title() const override { return "CVS Spam"; };
private:
static constexpr size_t read_size = 0x4000;
static constexpr size_t buffer_count = 3;
lfsr_word_t lfsr_v = 1;
bool chaos_mode{false};
NavigationView& nav_;
std::unique_ptr<ReplayThread> replay_thread{};
bool ready_signal{false};
bool thread_sync_complete{false};
std::filesystem::path current_file;
bool is_active() const;
void stop_tx();
void file_error(const std::filesystem::path& path, const std::string& error_details);
void refresh_list();
void start_tx(const uint32_t id);
void start_random_tx();
void on_tx_progress(const uint32_t progress);
uint32_t page = 1;
uint32_t current_page = 1;
std::vector<std::filesystem::path> file_list{};
MenuView menu_view{
{0, 0, 240, 180},
true};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
"Empty directory!"};
Button button_prev_page{
{0, 180, 50, 32},
"<"};
Button button_next_page{
{190, 180, 50, 32},
">"};
Text page_info{
{95, 188, 50, 16}};
Button button_send{
{0, 212, 75, 36},
"CALL"};
Button button_chaos{
{82, 212, 75, 36},
"CHAOS"};
Button button_stop{
{165, 212, 75, 36},
LanguageHelper::currentMessages[LANG_STOP]};
ProgressBar progressbar{
{0, 256, 240, 44}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* const p) {
const auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
ready_signal = true;
}
}};
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.progress);
}};
MessageHandlerRegistration message_handler_replay_thread_done{
Message::ID::ReplayThreadDone,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
if (message.return_code == ReplayThread::END_OF_FILE) {
if (chaos_mode) {
replay_thread.reset();
transmitter_model.disable();
ready_signal = false;
lfsr_v = lfsr_iterate(lfsr_v);
size_t random_index = lfsr_v % file_list.size();
menu_view.set_highlighted(random_index);
chThdSleepMilliseconds(100);
start_tx(random_index);
} else {
thread_sync_complete = true;
stop_tx();
}
} else if (message.return_code == ReplayThread::READ_ERROR) {
file_error(file_list[menu_view.highlighted_index()], "Read error during playback");
if (chaos_mode) {
replay_thread.reset();
transmitter_model.disable();
ready_signal = false;
lfsr_v = lfsr_iterate(lfsr_v);
size_t random_index = lfsr_v % file_list.size();
menu_view.set_highlighted(random_index);
chThdSleepMilliseconds(100);
start_tx(random_index);
}
}
}};
};
} // namespace ui::external_app::cvs_spam
+34
View File
@@ -0,0 +1,34 @@
// CVS Spam app by RocketGod (@rocketgod-git) https://betaskynet.com
// Original .cu8 files by @jimilinuxguy https://github.com/jimilinuxguy/customer-assistance-buttons-sdr
// If you can read this, you're a nerd. :P
// Come join us at https://discord.gg/thepiratesreborn
#include "ui.hpp"
#include "cvs_spam.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::cvs_spam {
void initialize_app(NavigationView& nav) {
nav.push<CVSSpamView>();
}
} // namespace ui::external_app::cvs_spam
extern "C" {
__attribute__((section(".external_app.app_cvs_spam.application_information"), used)) application_information_t _application_information_cvs_spam = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::cvs_spam::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "CVS Spam",
/*.bitmap_data = */ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81},
/*.icon_color = */ ui::Color::red().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'R', 'E', 'P'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+64
View File
@@ -106,7 +106,58 @@ set(EXTCPPSRC
#acars
external/acars_rx/main.cpp
external/acars_rx/acars_app.cpp
#shoppingcart_lock
external/shoppingcart_lock/main.cpp
external/shoppingcart_lock/shoppingcart_lock.cpp
#ookbrute
external/ookbrute/main.cpp
external/ookbrute/ui_ookbrute.cpp
#ook_editor
external/ook_editor/main.cpp
external/ook_editor/ui_ook_editor.cpp
#cvs_spam
external/cvs_spam/main.cpp
external/cvs_spam/cvs_spam.cpp
#flippertx
external/flippertx/main.cpp
external/flippertx/ui_flippertx.cpp
#remote
external/remote/main.cpp
external/remote/ui_remote.cpp
#mcu_temperature
external/mcu_temperature/main.cpp
external/mcu_temperature/mcu_temperature.cpp
#fmradio
external/fmradio/main.cpp
external/fmradio/ui_fmradio.cpp
#tuner
external/tuner/main.cpp
external/tuner/ui_tuner.cpp
#metronome
external/metronome/main.cpp
external/metronome/ui_metronome.cpp
#app_manager
external/app_manager/main.cpp
external/app_manager/ui_app_manager.cpp
# whip calculator
external/antenna_length/main.cpp
external/antenna_length/ui_whipcalc.cpp
# wav viewer
external/wav_view/main.cpp
external/wav_view/ui_view_wav.cpp
)
set(EXTAPPLIST
@@ -128,6 +179,7 @@ set(EXTAPPLIST
foxhunt_rx
audio_test
wardrivemap
cvs_spam
tpmsrx
protoview
adsbtx
@@ -135,4 +187,16 @@ set(EXTAPPLIST
sstvtx
random_password
#acars_rx
ookbrute
ook_editor
shoppingcart_lock
flippertx
remote
mcu_temperature
fmradio
tuner
metronome
app_manager
antenna_length
view_wav
)
+88 -1
View File
@@ -48,6 +48,19 @@ MEMORY
ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k
ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k
ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k
ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k
ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k
ram_external_app_ookbrute(rwx) : org = 0xADCC0000, len = 32k
ram_external_app_flippertx(rwx) : org = 0xADCD0000, len = 32k
ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k
ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k
ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k
ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k
ram_external_app_antenna_length(rwx) : org = 0xADD50000, len = 32k
ram_external_app_view_wav(rwx) : org = 0xADD60000, len = 32k
}
SECTIONS
@@ -166,7 +179,6 @@ SECTIONS
*(*ui*external_app*tpmsrx*);
} > ram_external_app_tpmsrx
.external_app_protoview : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_protoview.application_information));
@@ -204,7 +216,82 @@ SECTIONS
*(*ui*external_app*acars_rx*);
} > ram_external_app_acars_rx
.external_app_shoppingcart_lock : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_shoppingcart_lock.application_information));
*(*ui*external_app*shoppingcart_lock*);
} > ram_external_app_shoppingcart_lock
.external_app_cvs_spam : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_cvs_spam.application_information));
*(*ui*external_app*cvs_spam*);
} > ram_external_app_cvs_spam
.external_app_ookbrute : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_ookbrute.application_information));
*(*ui*external_app*ookbrute*);
} > ram_external_app_ookbrute
.external_app_ook_editor : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_ook_editor.application_information));
*(*ui*external_app*ook_editor*);
} > ram_external_app_ook_editor
.external_app_flippertx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_flippertx.application_information));
*(*ui*external_app*flippertx*);
} > ram_external_app_flippertx
.external_app_remote : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_remote.application_information));
*(*ui*external_app*remote*);
} > ram_external_app_remote
.external_app_mcu_temperature : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_mcu_temperature.application_information));
*(*ui*external_app*mcu_temperature*);
} > ram_external_app_mcu_temperature
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_fmradio.application_information));
*(*ui*external_app*fmradio*);
} > ram_external_app_fmradio
.external_app_tuner : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tuner.application_information));
*(*ui*external_app*tuner*);
} > ram_external_app_tuner
.external_app_metronome : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_metronome.application_information));
*(*ui*external_app*metronome*);
} > ram_external_app_metronome
.external_app_app_manager : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_app_manager.application_information));
*(*ui*external_app*app_manager*);
} > ram_external_app_app_manager
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_antenna_length.application_information));
*(*ui*external_app*antenna_length*);
} > ram_external_app_antenna_length
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_view_wav.application_information));
*(*ui*external_app*view_wav*);
} > ram_external_app_view_wav
}
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_extsensors.application_information"),
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::DEBUG,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_flippertx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::flippertx {
void initialize_app(ui::NavigationView& nav) {
nav.push<FlipperTxView>();
}
} // namespace ui::external_app::flippertx
extern "C" {
__attribute__((section(".external_app.app_flippertx.application_information"), used)) application_information_t _application_information_flippertx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::flippertx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "FlipperTx",
/*.bitmap_data = */ {
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0xE0,
0x07,
0xF0,
0x0F,
0x30,
0x0C,
0x30,
0x0C,
0xF0,
0x0F,
0xF0,
0x0F,
0x70,
0x0D,
0xB0,
0x0E,
0x70,
0x0D,
0xB0,
0x0E,
0xF0,
0x0F,
0xE0,
0x07,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_ookstreaming */ {'P', 'O', 'S', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+285
View File
@@ -0,0 +1,285 @@
/*
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_flippertx.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "buffer_exchange.hpp"
using namespace portapack;
using namespace ui;
namespace ui::external_app::flippertx {
void FlipperTxView::focus() {
button_browse.focus();
}
void FlipperTxView::set_ready() {
ready_signal = true;
}
FlipperTxView::FlipperTxView(NavigationView& nav)
: nav_{nav} {
add_children({&button_startstop,
&field_frequency,
&tx_view,
&field_filename,
&button_browse});
button_startstop.on_select = [this](Button&) {
if (is_running) {
stop();
} else {
start();
}
};
button_browse.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".sub");
open_view->push_dir(subghz_dir);
open_view->on_changed = [this](std::filesystem::path new_file_path) {
if (on_file_changed(new_file_path)) {
; // tif (button_startstop.parent()) button_startstop.focus(); parent_ is null error.....
}
};
};
}
bool FlipperTxView::on_file_changed(std::filesystem::path new_file_path) {
submeta = read_flippersub_file(new_file_path);
filename = "";
if (!submeta.is_valid()) {
field_filename.set_text("File: err, bad file");
return false;
}
proto = submeta.value().protocol;
if (proto != FLIPPER_PROTO_RAW && proto != FLIPPER_PROTO_BINRAW) { // temp disabled
field_filename.set_text("File: err, not supp. proto");
return false;
}
preset = submeta.value().preset;
if (preset != FLIPPER_PRESET_OOK) {
field_filename.set_text("File: err, not supp. preset");
return false;
}
te = submeta.value().te;
binraw_bit_count = submeta.value().binraw_bit_count;
/*if (binraw_bit_count >= 512 * 800) {
field_filename.set_text("File: err, too long"); // should stream, but not in this scope YET
return false;
}*/
field_filename.set_text("File: " + new_file_path.string());
filename = new_file_path;
return true;
}
void FlipperTxView::stop() {
if (is_running && replay_thread) replay_thread.reset();
is_running = false;
ready_signal = false;
transmitter_model.disable();
baseband::shutdown();
button_startstop.set_text(LanguageHelper::currentMessages[LANG_START]);
}
bool FlipperTxView::start() {
if (filename.empty()) return false;
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
transmitter_model.enable();
button_startstop.set_text(LanguageHelper::currentMessages[LANG_STOP]);
// start thread
replay_thread = std::make_unique<FlipperPlayThread>(
filename,
1024, 4,
&ready_signal,
submeta.value().protocol,
submeta.value().te,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
is_running = true;
return true;
}
void FlipperTxView::on_tx_progress(const bool done) {
if (done) {
if (is_running) {
stop();
}
}
}
FlipperTxView::~FlipperTxView() {
stop();
}
FlipperPlayThread::FlipperPlayThread(
std::filesystem::path filename,
size_t read_size,
size_t buffer_count,
bool* ready_signal,
FlipperProto proto,
uint16_t te,
std::function<void(uint32_t return_code)> terminate_callback)
: config{read_size, buffer_count},
filename{filename},
ready_sig{ready_signal},
proto{proto},
te{te},
terminate_callback{std::move(terminate_callback)} {
thread = chThdCreateFromHeap(NULL, 6 * 1024, NORMALPRIO + 10, FlipperPlayThread::static_fn, this);
}
FlipperPlayThread::~FlipperPlayThread() {
if (thread) {
chThdTerminate(thread);
if (thread->p_refs) chThdWait(thread);
thread = nullptr;
}
}
msg_t FlipperPlayThread::static_fn(void* arg) {
auto obj = static_cast<FlipperPlayThread*>(arg);
const auto return_code = obj->run();
if (obj->terminate_callback) {
obj->terminate_callback(return_code);
}
chThdExit(0);
return 0;
}
uint32_t FlipperPlayThread::run() {
BasebandReplay replay{&config};
BufferExchange buffers{&config};
StreamBuffer* prefill_buffer{nullptr};
int32_t read_result = 0;
// assume it is ok, since prev we checked it.
// open the sub file
File f; // don't worry, destructor will close it.
auto error = f.open(filename);
if (error) return READ_ERROR;
// seek to the first data
if (proto == FLIPPER_PROTO_RAW)
seek_flipper_raw_first_data(f);
else {
seek_flipper_binraw_first_data(f);
}
// Wait for FIFOs to be allocated in baseband
// Wait for _view to tell us that the buffers are ready
while (!(*ready_sig) && !chThdShouldTerminate()) {
chThdSleep(100);
};
uint8_t readall = 0;
int32_t endsignals[3] = {0, 42069, 613379}; // unlikely a valid data
// While empty buffers fifo is not empty...
while (!buffers.empty() && !chThdShouldTerminate()) {
prefill_buffer = buffers.get_prefill();
if (prefill_buffer == nullptr) {
buffers.put_app(prefill_buffer);
} else {
size_t c = 0;
for (c = 0; c < config.read_size / 4;) {
read_result = 0;
if (0 == readall) {
if (proto == FLIPPER_PROTO_RAW) {
auto data = read_flipper_raw_next_data(f);
if (!data.is_valid()) {
((int32_t*)prefill_buffer->data())[c] = endsignals[++readall];
} else {
read_result = data.value();
((int32_t*)prefill_buffer->data())[c] = read_result;
}
c++;
} else { // BINRAW
auto data = read_flipper_binraw_next_data(f);
if (!data.is_valid())
readall = 1;
else {
read_result = data.value();
// add 8 data
for (uint8_t bit = 0; bit < 8; bit++) {
((int32_t*)prefill_buffer->data())[c + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te);
c++;
}
}
}
} else {
((int32_t*)prefill_buffer->data())[c] = endsignals[readall];
if (readall == 1) readall++;
c++;
}
}
prefill_buffer->set_size(config.read_size);
buffers.put(prefill_buffer);
}
};
baseband::set_fifo_data(nullptr);
if (readall) return END_OF_FILE;
while (!chThdShouldTerminate()) {
auto buffer = buffers.get();
int32_t read_result = 0;
for (size_t d = 0; d < buffer->capacity() / 4; d++) {
read_result = -133469;
if (!readall) {
if (proto == FLIPPER_PROTO_RAW) {
auto data = read_flipper_raw_next_data(f);
if (!data.is_valid()) {
readall = 1;
} else {
read_result = data.value();
}
} else {
auto data = read_flipper_binraw_next_data(f);
if (!data.is_valid())
readall = 1;
else {
read_result = data.value();
// add 8 data
for (uint8_t bit = 0; bit < 8; bit++) {
((int32_t*)prefill_buffer->data())[d + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te);
}
d += 7;
continue;
}
}
}
if (readall >= 1 && readall <= 2) {
read_result = endsignals[readall++];
}
((int32_t*)buffer->data())[d] = read_result;
}
buffer->set_size(buffer->capacity());
buffers.put(buffer);
if (readall == 3) return END_OF_FILE;
}
return TERMINATED;
}
} // namespace ui::external_app::flippertx
+169
View File
@@ -0,0 +1,169 @@
/*
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
*/
#ifndef __UI_flippertx_H__
#define __UI_flippertx_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "metadata_file.hpp"
#include "flipper_subfile.hpp"
#include "ui_fileman.hpp"
#include "baseband_api.hpp"
using namespace ui;
namespace ui::external_app::flippertx {
#define OOK_SAMPLERATE 2280000U
class FlipperPlayThread;
class FlipperTxView : public View {
public:
FlipperTxView(NavigationView& nav);
~FlipperTxView();
void focus() override;
std::string title() const override {
return "FlipperTx";
};
private:
NavigationView& nav_;
TxRadioState radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
TxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
TransmitterView2 tx_view{
{11 * 8, 0 * 16},
/*short_ui*/ true};
app_settings::SettingsManager settings_{
"tx_flippertx", app_settings::Mode::TX};
TextField field_filename{
{0, 2 * 16, 300, 1 * 8},
"File: -"};
Button button_startstop{
{1, 6 * 16, 96, 24},
LanguageHelper::currentMessages[LANG_START]};
Button button_browse{
{1, 3 * 16 + 3, 96, 24},
LanguageHelper::currentMessages[LANG_BROWSE]};
bool is_running{false};
bool start();
void stop();
void set_ready();
void on_tx_progress(const bool done);
bool on_file_changed(std::filesystem::path new_file_path);
std::filesystem::path filename = {};
FlipperProto proto = FLIPPER_PROTO_UNSUPPORTED;
FlipperPreset preset = FLIPPER_PRESET_UNK;
uint16_t te = 0; // for binraw
uint32_t binraw_bit_count = 0;
bool ready_signal = false;
std::unique_ptr<FlipperPlayThread> replay_thread{};
Optional<flippersub_metadata> submeta{};
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);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* const p) {
const auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
this->set_ready();
}
}};
MessageHandlerRegistration message_handler_replay_thread_done{
Message::ID::ReplayThreadDone,
[this](const Message* const p) {
// const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
(void)p;
// stop(); //don't stop for now, stop when i get the tx finished msg
}};
};
struct BasebandReplay {
BasebandReplay(ReplayConfig* const config) {
baseband::replay_start(config);
}
~BasebandReplay() {
// baseband::replay_stop(); // wont, since we need to send out that is in the buffer
}
};
class FlipperPlayThread {
public:
FlipperPlayThread(
std::filesystem::path,
size_t read_size,
size_t buffer_count,
bool* ready_signal,
FlipperProto proto,
uint16_t te,
std::function<void(uint32_t return_code)> terminate_callback);
~FlipperPlayThread();
FlipperPlayThread(const FlipperPlayThread&) = delete;
FlipperPlayThread(FlipperPlayThread&&) = delete;
FlipperPlayThread& operator=(const FlipperPlayThread&) = delete;
FlipperPlayThread& operator=(FlipperPlayThread&&) = delete;
const ReplayConfig& state() const {
return config;
};
enum FlipperPlayThread_return {
READ_ERROR = 0,
END_OF_FILE,
TERMINATED
};
private:
ReplayConfig config;
std::filesystem::path filename;
bool* ready_sig;
FlipperProto proto;
uint16_t te;
std::function<void(uint32_t return_code)> terminate_callback;
Thread* thread{nullptr};
static msg_t static_fn(void* arg);
uint32_t run();
};
}; // namespace ui::external_app::flippertx
#endif /*__UI_flippertx_H__*/
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_fmradio.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::fmradio {
void initialize_app(ui::NavigationView& nav) {
nav.push<FmRadioView>();
}
} // namespace ui::external_app::fmradio
extern "C" {
__attribute__((section(".external_app.app_fmradio.application_information"), used)) application_information_t _application_information_fmradio = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::fmradio::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "FM Radio",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x04,
0x20,
0x12,
0x48,
0x8A,
0x51,
0xCA,
0x53,
0xCA,
0x53,
0x8A,
0x51,
0x12,
0x48,
0x84,
0x21,
0xC0,
0x03,
0x40,
0x02,
0x60,
0x06,
0x20,
0x04,
0x30,
0x0C,
0xF0,
0x0F,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_wfm_audio */ {'P', 'W', 'F', 'M'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+168
View File
@@ -0,0 +1,168 @@
/*
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_fmradio.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace modems;
using namespace ui;
namespace ui::external_app::fmradio {
void FmRadioView::focus() {
field_frequency.focus();
}
FmRadioView::FmRadioView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
add_children({&rssi,
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
&btn_fav_save,
&txt_save_help,
&btn_fav_0,
&btn_fav_1,
&btn_fav_2,
&btn_fav_3,
&btn_fav_4,
&btn_fav_5,
&btn_fav_6,
&btn_fav_7,
&btn_fav_8,
&btn_fav_9,
&audio,
&waveform});
txt_save_help.visible(false);
for (uint8_t i = 0; i < 12; ++i) {
if (freq_fav_list[i] == 0) {
freq_fav_list[i] = 87000000;
}
}
if (field_frequency.value() == 0) {
field_frequency.set_value(87000000);
}
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
field_frequency.set_step(25000);
receiver_model.enable();
audio::output::start();
btn_fav_0.on_select = [this](Button&) {
on_btn_clicked(0);
};
btn_fav_1.on_select = [this](Button&) {
on_btn_clicked(1);
};
btn_fav_2.on_select = [this](Button&) {
on_btn_clicked(2);
};
btn_fav_3.on_select = [this](Button&) {
on_btn_clicked(3);
};
btn_fav_4.on_select = [this](Button&) {
on_btn_clicked(4);
};
btn_fav_5.on_select = [this](Button&) {
on_btn_clicked(5);
};
btn_fav_6.on_select = [this](Button&) {
on_btn_clicked(6);
};
btn_fav_7.on_select = [this](Button&) {
on_btn_clicked(7);
};
btn_fav_8.on_select = [this](Button&) {
on_btn_clicked(8);
};
btn_fav_9.on_select = [this](Button&) {
on_btn_clicked(9);
};
btn_fav_save.on_select = [this](Button&) {
save_fav = !save_fav;
txt_save_help.set_text(save_fav ? "Select slot" : "");
txt_save_help.visible(save_fav);
txt_save_help.set_dirty();
};
update_fav_btn_texts();
}
void FmRadioView::on_btn_clicked(uint8_t i) {
if (save_fav) {
save_fav = false;
freq_fav_list[i] = field_frequency.value();
update_fav_btn_texts();
txt_save_help.visible(save_fav);
txt_save_help.set_text("");
txt_save_help.set_dirty();
return;
}
field_frequency.set_value(freq_fav_list[i]);
}
std::string FmRadioView::to_nice_freq(rf::Frequency freq) {
std::string nice = to_string_dec_uint(freq / 1000000);
nice += ".";
nice += to_string_dec_uint((freq / 10000) % 100);
return nice;
}
void FmRadioView::update_fav_btn_texts() {
btn_fav_0.set_text(to_nice_freq(freq_fav_list[0]));
btn_fav_1.set_text(to_nice_freq(freq_fav_list[1]));
btn_fav_2.set_text(to_nice_freq(freq_fav_list[2]));
btn_fav_3.set_text(to_nice_freq(freq_fav_list[3]));
btn_fav_4.set_text(to_nice_freq(freq_fav_list[4]));
btn_fav_5.set_text(to_nice_freq(freq_fav_list[5]));
btn_fav_6.set_text(to_nice_freq(freq_fav_list[6]));
btn_fav_7.set_text(to_nice_freq(freq_fav_list[7]));
btn_fav_8.set_text(to_nice_freq(freq_fav_list[8]));
btn_fav_9.set_text(to_nice_freq(freq_fav_list[9]));
}
FmRadioView::~FmRadioView() {
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
}
void FmRadioView::on_audio_spectrum() {
for (size_t i = 0; i < audio_spectrum_data->db.size(); i++)
audio_spectrum[i] = ((int16_t)audio_spectrum_data->db[i] - 127) * 256;
waveform.set_dirty();
}
} // namespace ui::external_app::fmradio
+150
View File
@@ -0,0 +1,150 @@
/*
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
/*
FUTURE TODO: implement search function.
*/
#ifndef __UI_fmradio_H__
#define __UI_fmradio_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "ui_freq_field.hpp"
#include "ui_spectrum.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::fmradio {
#define FMR_BTNGRID_TOP 60
class FmRadioView : public View {
public:
FmRadioView(NavigationView& nav);
FmRadioView& operator=(const FmRadioView&) = delete;
FmRadioView(const FmRadioView&) = delete;
~FmRadioView();
void focus() override;
std::string title() const override { return "FM radio"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
int16_t audio_spectrum[128]{0};
bool audio_spectrum_update = false;
AudioSpectrum* audio_spectrum_data{nullptr};
rf::Frequency freq_fav_list[12] = {0};
app_settings::SettingsManager settings_{
"rx_fmradio",
app_settings::Mode::RX,
{{"favlist0"sv, &freq_fav_list[0]},
{"favlist1"sv, &freq_fav_list[1]},
{"favlist2"sv, &freq_fav_list[2]},
{"favlist3"sv, &freq_fav_list[3]},
{"favlist4"sv, &freq_fav_list[4]},
{"favlist5"sv, &freq_fav_list[5]},
{"favlist6"sv, &freq_fav_list[6]},
{"favlist7"sv, &freq_fav_list[7]},
{"favlist8"sv, &freq_fav_list[8]},
{"favlist9"sv, &freq_fav_list[9]},
{"favlist10"sv, &freq_fav_list[10]},
{"favlist11"sv, &freq_fav_list[11]}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
TextField txt_save_help{
{2, FMR_BTNGRID_TOP + 6 * 34 - 20, 12 * 8, 16},
" "};
Audio audio{
{21 * 8, 10, 6 * 8, 4}};
Waveform waveform{
{0, 20, 30 * 8, 2 * 16},
audio_spectrum,
128,
0,
false,
Theme::getInstance()->bg_darkest->foreground};
Button btn_fav_0{{2, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
Button btn_fav_1{{2 + 15 * 8, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
Button btn_fav_2{{2, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
Button btn_fav_3{{2 + 15 * 8, FMR_BTNGRID_TOP + 1 * 34, 10 * 8, 28}, "---"};
Button btn_fav_4{{2, FMR_BTNGRID_TOP + 2 * 34, 10 * 8, 28}, "---"};
Button btn_fav_5{{2 + 15 * 8, FMR_BTNGRID_TOP + 2 * 34, 10 * 8, 28}, "---"};
Button btn_fav_6{{2, FMR_BTNGRID_TOP + 3 * 34, 10 * 8, 28}, "---"};
Button btn_fav_7{{2 + 15 * 8, FMR_BTNGRID_TOP + 3 * 34, 10 * 8, 28}, "---"};
Button btn_fav_8{{2, FMR_BTNGRID_TOP + 4 * 34, 10 * 8, 28}, "---"};
Button btn_fav_9{{2 + 15 * 8, FMR_BTNGRID_TOP + 4 * 34, 10 * 8, 28}, "---"};
Button btn_fav_save{{2, FMR_BTNGRID_TOP + 6 * 34, 7 * 8, 1 * 28}, "Save"};
bool save_fav = false;
void on_btn_clicked(uint8_t i);
void update_fav_btn_texts();
std::string to_nice_freq(rf::Frequency freq);
void on_audio_spectrum();
MessageHandlerRegistration message_handler_audio_spectrum{
Message::ID::AudioSpectrum,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const AudioSpectrumMessage*>(p);
audio_spectrum_data = message.data;
audio_spectrum_update = true;
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
if (audio_spectrum_update) {
audio_spectrum_update = false;
on_audio_spectrum();
}
}};
};
} // namespace ui::external_app::fmradio
#endif /*__UI_fmradio_H__*/
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_font_viewer.application_information"),
},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::DEBUG,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_foxhunt_rx.application_information"),
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_am_audio */ {'P', 'A', 'M', 'A'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_gpssim.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_gpssim */ {'P', 'G', 'P', 'S'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_jammer.application_information"), used
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_jammer */ {'P', 'J', 'A', 'M'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_keyfob.application_information"), used
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_keyfob */ {'P', 'O', 'O', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lcr.application_information"), used))
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk */ {'P', 'A', 'F', 'T'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_lge.application_information"), used))
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_fsktx */ {'P', 'F', 'S', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "mcu_temperature.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::mcu_temperature {
void initialize_app(ui::NavigationView& nav) {
nav.push<McuTemperatureView>();
}
} // namespace ui::external_app::mcu_temperature
extern "C" {
__attribute__((section(".external_app.app_mcu_temperature.application_information"), used)) application_information_t _application_information_mcu_temperature = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::mcu_temperature::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Temperature",
/*.bitmap_data = */ {
0x00,
0x00,
0x20,
0x00,
0x70,
0x3E,
0x88,
0x00,
0x88,
0x00,
0x88,
0x3E,
0x88,
0x00,
0x88,
0x00,
0x88,
0x3E,
0x88,
0x00,
0x04,
0x01,
0x74,
0x01,
0x04,
0x01,
0x88,
0x00,
0x70,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::DEBUG,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,91 @@
#include "mcu_temperature.hpp"
#include "ui_painter.hpp"
#include "portapack.hpp"
using namespace portapack;
namespace ui::external_app::mcu_temperature {
void McuTemperatureWidget::paint(Painter& painter) {
const auto logger = portapack::temperature_logger;
const auto rect = screen_rect();
const Color color_background{0, 0, 64};
const Color color_foreground = Theme::getInstance()->fg_green->foreground;
const Color color_reticle{128, 128, 128};
const auto graph_width = static_cast<int>(logger.capacity()) * bar_width;
const Rect graph_rect{
rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8,
graph_width, rect.height()};
const Rect frame_rect{
graph_rect.left() - 1, graph_rect.top() - 1,
graph_rect.width() + 2, graph_rect.height() + 2};
painter.draw_rectangle(frame_rect, color_reticle);
painter.fill_rectangle(graph_rect, color_background);
const auto history = logger.history();
for (size_t i = 0; i < history.size(); i++) {
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
const auto sample = history[i];
const auto temp = temperature(sample);
const auto y = screen_y(temp, graph_rect);
const Dim bar_height = graph_rect.bottom() - y;
painter.fill_rectangle({x, y, bar_width, bar_height}, color_foreground);
}
if (!history.empty()) {
const auto sample = history.back();
const auto temp = temperature(sample);
const auto last_y = screen_y(temp, graph_rect);
const Coord x = graph_rect.right() + 8;
const Coord y = last_y - 8;
painter.draw_string({x, y}, style(), temperature_str(temp));
}
const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
for (auto temp = display_temp_min; temp <= display_temp_max; temp += 10) {
const int32_t tick_length = 6;
const auto tick_x = graph_rect.left() - tick_length;
const auto tick_y = screen_y(temp, graph_rect);
painter.fill_rectangle({tick_x, tick_y, tick_length, 1}, color_reticle);
const auto text_x = graph_rect.left() - temp_len * 8 - 8;
const auto text_y = tick_y - 8;
painter.draw_string({text_x, text_y}, style(), temperature_str(temp));
}
}
McuTemperatureWidget::temperature_t McuTemperatureWidget::temperature(const sample_t sensor_value) const {
// Scaling is different for MAX2837 vs MAX2839 so it's now done in the respective chip-specific module
return sensor_value;
}
std::string McuTemperatureWidget::temperature_str(const temperature_t temperature) const {
return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
}
Coord McuTemperatureWidget::screen_y(
const temperature_t temperature,
const Rect& rect) const {
int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
return y_limit;
}
McuTemperatureView::McuTemperatureView(NavigationView& nav) {
add_children({
&text_title,
&temperature_widget,
&button_done,
});
button_done.on_select = [&nav](Button&) { nav.pop(); };
}
void McuTemperatureView::focus() {
button_done.focus();
}
} // namespace ui::external_app::mcu_temperature
@@ -0,0 +1,90 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __MCU_TEMPERATURE_H__
#define __MCU_TEMPERATURE_H__
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "rffc507x.hpp"
#include "portapack.hpp"
#include "memory_map.hpp"
#include "irq_controls.hpp"
#include <functional>
#include <utility>
namespace ui::external_app::mcu_temperature {
class McuTemperatureWidget : public Widget {
public:
explicit McuTemperatureWidget(
Rect parent_rect)
: Widget{parent_rect} {
}
void paint(Painter& painter) override;
private:
using sample_t = uint32_t;
using temperature_t = int32_t;
temperature_t temperature(const sample_t sensor_value) const;
Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const;
std::string temperature_str(const temperature_t temperature) const;
static constexpr temperature_t display_temp_min = -10; // Accomodate negative values, present in cold startup cases
static constexpr temperature_t display_temp_scale = 3;
static constexpr int bar_width = 1;
static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
};
class McuTemperatureView : public View {
public:
explicit McuTemperatureView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Temperature"; };
private:
Text text_title{
{76, 16, 240, 16},
"Temperature",
};
McuTemperatureWidget temperature_widget{
{0, 40, 240, 180},
};
Button button_done{
{72, 264, 96, 24},
"Done"};
};
} // namespace ui::external_app::mcu_temperature
#endif /*__MCU_TEMPERATURE_H__*/
+84
View File
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2024 Bernd
*
* 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_metronome.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::metronome {
void initialize_app(ui::NavigationView& nav) {
nav.push<MetronomeView>();
}
} // namespace ui::external_app::metronome
extern "C" {
__attribute__((section(".external_app.app_metronome.application_information"), used)) application_information_t _application_information_metronome = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::metronome::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Metronome",
/*.bitmap_data = */ {
0x00,
0x00,
0xC0,
0x43,
0x20,
0x66,
0xA0,
0x34,
0x20,
0x18,
0xB0,
0x0C,
0x10,
0x04,
0x10,
0x06,
0x10,
0x0B,
0x98,
0x19,
0x08,
0x10,
0xF8,
0x1F,
0xF8,
0x1F,
0xF8,
0x1F,
0xF0,
0x0F,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'B', 'P'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+177
View File
@@ -0,0 +1,177 @@
/*
* copyleft 2024 sommermorgentraum
*
* 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_metronome.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "portapack.hpp"
using namespace portapack;
namespace ui::external_app::metronome {
MetronomeView::MetronomeView(NavigationView& nav)
: nav_{nav} {
baseband::run_prepared_image(portapack::memory::map::m4_code.base()); // proc_audio_beep baseband is external too
add_children({
&labels,
&field_volume,
&button_play_stop,
&field_rythm_unaccent_time,
&field_rythm_accent_time,
&field_accent_beep_tune,
&field_unaccent_beep_tune,
&field_beep_flash_duration,
&field_bpm,
&progressbar,
});
field_bpm.set_value(120);
field_rythm_accent_time.set_value(4);
field_rythm_unaccent_time.set_value(4);
field_accent_beep_tune.set_value(880);
field_unaccent_beep_tune.set_value(440);
field_beep_flash_duration.set_value(100);
button_play_stop.on_select = [this]() {
if (playing_) {
stop_play();
} else {
play();
}
};
field_volume.set_value(0); // seems that a change is required to force update, so setting to 0 first
field_volume.set_value(99);
audio::set_rate(audio::Rate::Hz_48000);
audio::output::start();
}
MetronomeView::~MetronomeView() {
should_exit = true;
if (thread) {
chThdWait(thread);
thread = nullptr;
}
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
}
void MetronomeView::focus() {
field_bpm.focus();
}
void MetronomeView::stop_play() {
if (playing_) {
playing_ = false;
button_play_stop.set_bitmap(&bitmap_icon_replay);
baseband::request_beep_stop();
progressbar.set_value(0);
progressbar.set_style(Theme::getInstance()->fg_light);
}
}
void MetronomeView::play() {
if (!playing_) {
playing_ = true;
current_beat_ = 0;
button_play_stop.set_bitmap(&bitmap_icon_sleep);
if (!thread) {
thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, MetronomeView::static_fn, this);
}
}
}
void MetronomeView::beep_accent_beat() {
baseband::request_audio_beep(field_accent_beep_tune.value(), 48000, field_beep_flash_duration.value());
}
void MetronomeView::beep_unaccent_beat() {
baseband::request_audio_beep(field_unaccent_beep_tune.value(), 48000, field_beep_flash_duration.value());
}
// TODO: draw the beat
// void MetronomeView::paint(Painter& painter) {
// View::paint(painter);
// painter.fill_rectangle(
// {visual_x, visual_y, visual_width, visual_height},
// Theme::getInstance()->bg_darkest->background);
// if (playing_) {
// const bool is_accent_beat = (current_beat_ % field_rythm_accent_time.value()) == 0;
// const Color beat_color = is_accent_beat ?
// Theme::getInstance()->fg_red->foreground :
// Theme::getInstance()->fg_green->foreground;
// painter.fill_rectangle(
// {visual_x + visual_width/4,
// visual_y + visual_height/4,
// visual_width/2,
// visual_height/2},
// beat_color);
// }
// }
msg_t MetronomeView::static_fn(void* arg) {
auto obj = static_cast<MetronomeView*>(arg);
obj->run();
return 0;
}
void MetronomeView::run() {
while (!should_exit) {
if (!playing_) {
chThdSleepMilliseconds(100);
continue;
}
uint32_t base_interval = (60 * 1000) / field_bpm.value(); // quarter note as 1 beat
uint32_t beats_per_measure = field_rythm_unaccent_time.value(); // how many beates per bar
progressbar.set_max(beats_per_measure);
uint32_t beat_unit = field_rythm_accent_time.value(); // which note type (quarter, eighth, etc.) as 1 beat
uint32_t actual_interval = (base_interval * 4) / beat_unit; // e.g. when beat_unit==8 it's 1/2 of base_interval AKA eighths notes
uint32_t beat_in_measure = current_beat_ % beats_per_measure; // current beat in this bar (need to decide accent or unaccent)
progressbar.set_value(beat_in_measure + 1);
// accent beat is the first beat of this bar
if (beat_in_measure == 0) {
beep_accent_beat();
progressbar.set_style(Theme::getInstance()->fg_red);
} else {
beep_unaccent_beat();
progressbar.set_style(Theme::getInstance()->fg_green);
}
current_beat_++;
chThdSleepMilliseconds(actual_interval);
}
}
} // namespace ui::external_app::metronome
+126
View File
@@ -0,0 +1,126 @@
/*
* copyleft 2024 sommermorgentraum
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_METRONOME_H__
#define __UI_METRONOME_H__
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "audio.hpp"
#include "ch.h"
namespace ui::external_app::metronome {
class MetronomeView : public View {
public:
MetronomeView(NavigationView& nav);
~MetronomeView();
MetronomeView(const MetronomeView& other) = delete;
MetronomeView& operator=(const MetronomeView& other) = delete;
void focus() override;
std::string title() const override { return "Metronome"; };
private:
NavigationView& nav_;
void beep_accent_beat(); // e.g. 3 of 3/4 beat
void beep_unaccent_beat(); // e.g. 4 of 3/4 beat
void stop_play();
void play();
// void paint(Painter& painter) override;
Thread* thread{nullptr};
bool should_exit{false};
static msg_t static_fn(void* arg);
void run();
bool playing_{false};
uint32_t current_beat_{0};
Labels labels{
{{0 * 8, 1 * 16}, "BPM:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Accent Beep Tune:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Unaccent Beep Tune:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Rhythm:", Theme::getInstance()->fg_light->foreground},
{{(sizeof("Rhythm:") + 1) * 8 + 4 * 8, 4 * 16}, "/", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Beep Flash Duration:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Volume:", Theme::getInstance()->fg_light->foreground}};
NumberField field_bpm{
{(sizeof("BPM:") + 1) * 8, 1 * 16},
4,
{1, 1000},
1,
' '};
NumberField field_rythm_unaccent_time{// e.g. 3 in 3/4 beat
{(sizeof("Rhythm:") + 1) * 8, 4 * 16},
2,
{1, 99},
1,
' '};
NumberField field_rythm_accent_time{// e.g. 4 in 3/4 beat
{(sizeof("Rhythm:") + 1) * 8 + 5 * 8, 4 * 16},
2,
{1, 99},
1,
' '};
NumberField field_beep_flash_duration{
{(sizeof("Beep Flash Duration:") + 1) * 8, 5 * 16},
3,
{10, 999},
1,
' '};
NumberField field_accent_beep_tune{
{(sizeof("Accent Beep Tune:") + 1) * 8, 2 * 16},
5,
{380, 24000},
20,
' '};
NumberField field_unaccent_beep_tune{
{(sizeof("Unaccent Beep Tune:") + 1) * 8, 3 * 16},
5,
{380, 24000},
20,
' '};
AudioVolumeField field_volume{
{(sizeof("Volume:") + 1) * 8, 6 * 16}};
NewButton button_play_stop{
{0 * 16, 16 * 16, screen_width, screen_height - 16 * 16},
{},
&bitmap_icon_replay,
Theme::getInstance()->fg_red->foreground};
ProgressBar progressbar{
{0 * 16, 8 * 16, screen_width, screen_height - 14 * 16}};
};
} // namespace ui::external_app::metronome
#endif /*__UI_METRONOME_H__*/
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_morse_tx.application_information"), us
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_tones */ {'P', 'T', 'O', 'N'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+2 -3
View File
@@ -232,9 +232,8 @@ MorseView::MorseView(
set_foxhunt(foxhunt_code);
};
options_modulation.on_change = [this](size_t i, int32_t value) {
(void)i; // avoid unused warning
mode_cw = (bool)value;
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
mode_cw = (bool)v;
};
options_loop.on_change = [this](size_t i, uint32_t value) {
+7 -2
View File
@@ -41,6 +41,11 @@ using namespace morse;
namespace ui::external_app::morse_tx {
enum Modulation {
FM = 0,
CW = 1
};
class MorseView : public View {
public:
MorseView(NavigationView& nav);
@@ -137,8 +142,8 @@ class MorseView : public View {
OptionsField options_modulation{
{15 * 8, 10 * 8},
2,
{{"CW", true},
{"FM", false}}};
{{"CW", Modulation::CW},
{"FM", Modulation::FM}}};
OptionsField options_loop{
{9 * 8, 12 * 8},
+1
View File
@@ -76,6 +76,7 @@ __attribute__((section(".external_app.app_nrf_rx.application_information"), used
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_nrf_rx */ {'P', 'N', 'R', 'R'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
*
* 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_ook_editor.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::ook_editor {
void initialize_app(ui::NavigationView& nav) {
nav.push<OOKEditorAppView>();
}
} // namespace ui::external_app::ook_editor
extern "C" {
__attribute__((section(".external_app.app_ook_editor.application_information"), used)) application_information_t _application_information_ook_editor = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::ook_editor::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "OOKEditor",
/*.bitmap_data = */ {
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0xE0,
0x07,
0xF0,
0x0F,
0x30,
0x0C,
0x30,
0x0C,
0xF0,
0x0F,
0xF0,
0x0F,
0x70,
0x0D,
0xB0,
0x0E,
0x70,
0x0D,
0xB0,
0x0E,
0xF0,
0x0F,
0xE0,
0x07,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,283 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
*
* 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_ook_editor.hpp"
using namespace portapack;
using namespace ui;
namespace fs = std::filesystem;
namespace ui::external_app::ook_editor {
// give focus to set button
void OOKEditorAppView::focus() {
button_set.focus();
}
// update internal ook_data with GUI values
void OOKEditorAppView::update_ook_data_from_app() {
ook_data.frequency = field_frequency.value();
ook_data.sample_rate = field_sample_rate.selected_index_value();
ook_data.symbol_rate = field_symbol_rate.value();
ook_data.repeat = field_repeat.value();
ook_data.pause_symbol_duration = field_pause_symbol_duration.value();
}
// `start_tx` method: Configures and begins OOK data transmission with a specific message.
void OOKEditorAppView::start_tx() {
// check if there is a payload
if (ook_data.payload.length() < 1) {
text_app_status.set("Error: no payload to tx !!");
return;
}
progressbar.set_max(field_repeat.value()); // Size the progress bar accordingly to the number of repeat
is_transmitting = true; // set transmitting flag
button_send_stop.set_text(LanguageHelper::currentMessages[LANG_STOP]); // set button back to initial "start" state
start_ook_file_tx(ook_data); // start the transmission
}
// `stop_tx` method: Stops the transmission and resets the progress bar.
void OOKEditorAppView::stop_tx() {
// TODO: model stopped but message still spamming.
is_transmitting = false; // set transmitting flag
stop_ook_file_tx(); // stop transmission
progressbar.set_value(0); // Reset progress bar to 0
button_send_stop.set_text(LanguageHelper::currentMessages[LANG_START]); // set button back to initial "start" state
}
// `on_file_changed` method: Called when a new file is loaded; parses file data into variables
void OOKEditorAppView::on_file_changed(const fs::path& new_file_path) {
ook_data.payload.clear(); // Clear previous payload content
if (!read_ook_file(new_file_path, ook_data)) {
text_app_status.set("Error loading " + new_file_path.filename().string());
return;
}
field_frequency.set_value(ook_data.frequency);
field_symbol_rate.set_value(ook_data.symbol_rate);
field_repeat.set_value(ook_data.repeat);
field_pause_symbol_duration.set_value(ook_data.pause_symbol_duration);
field_sample_rate.set_by_value(ook_data.sample_rate);
text_payload.set(ook_data.payload);
button_send_stop.focus();
text_app_status.set("Loaded: " + new_file_path.filename().string());
}
// `on_tx_progress` method: Updates the progress bar based on transmission progress.
void OOKEditorAppView::on_tx_progress(const uint32_t progress, const bool done) {
if (is_transmitting) progressbar.set_value(progress); // Update progress bar value
if (done) {
stop_tx(); // Stop transmission when progress reaches maximum
}
}
// `draw_waveform` method: Draws the waveform on the UI based on the payload data
void OOKEditorAppView::draw_waveform() {
// Padding reason:
// In real-world scenarios, the signal would always start low and return low after turning off the radio.
// `waveform_buffer` only controls drawing; the actual send logic is handled by frame_fragments.
size_t length = ook_data.payload.length();
// Ensure waveform length does not exceed buffer size
if (length + (PADDING_LEFT + PADDING_RIGHT) >= WAVEFORM_BUFFER_SIZE) {
length = WAVEFORM_BUFFER_SIZE - (PADDING_LEFT + PADDING_RIGHT);
}
// Left padding
for (size_t i = 0; i < PADDING_LEFT; i++) {
waveform_buffer[i] = 0;
}
// Draw the actual waveform
for (size_t n = 0; n < length; n++) {
waveform_buffer[n + PADDING_LEFT] = (ook_data.payload[n] == '0') ? 0 : 1;
}
// Right padding
for (size_t i = length + PADDING_LEFT; i < WAVEFORM_BUFFER_SIZE; i++) {
waveform_buffer[i] = 0;
}
waveform.set_length(length + PADDING_LEFT + PADDING_RIGHT);
waveform.set_dirty();
}
// build a new path+file, make some tests, call save_ook_to_file
void OOKEditorAppView::on_save_file(const std::string value) {
// check if there is a payload, else Error
if (ook_data.payload.length() < 1) {
text_app_status.set("Err: can't save, no payload !");
return;
}
ensure_directory(ook_editor_dir);
auto new_path = ook_editor_dir / value + ".OOK";
if (save_ook_to_file(new_path)) {
text_app_status.set("Saved to " + new_path.string());
} else {
text_app_status.set("Error saving " + new_path.string());
}
}
// update ook_data from GUI and save
bool OOKEditorAppView::save_ook_to_file(const std::filesystem::path& path) {
update_ook_data_from_app();
return save_ook_file(ook_data, path);
}
// Destructor for `OOKEditorAppView`: Disables the transmitter and shuts down the baseband
OOKEditorAppView::~OOKEditorAppView() {
stop_ook_file_tx();
baseband::shutdown();
}
// Constructor for `OOKEditorAppView`: Sets up the app view and initializes UI elements
OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
: nav_{nav} {
// load OOK baseband
baseband::run_image(portapack::spi_flash::image_tag_ook);
// add all the widgets
add_children({&field_frequency,
&tx_view,
&button_send_stop,
&label_step,
&field_step,
&label_sample_rate,
&field_sample_rate,
&label_symbol_rate,
&field_symbol_rate,
&label_symbol_rate_unit,
&text_payload,
&button_set,
&progressbar,
&label_repeat,
&field_repeat,
&label_pause_symbol_duration,
&field_pause_symbol_duration,
&label_pause_symbol_duration_unit,
&label_payload,
&text_app_status,
&label_waveform,
&waveform,
&button_open,
&button_save});
// Initialize default values for controls
field_symbol_rate.set_value(100);
field_pause_symbol_duration.set_value(100);
field_repeat.set_value(4);
// Configure open ook file button
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".OOK");
ensure_directory(ook_editor_dir);
open_view->push_dir(ook_editor_dir);
open_view->on_changed = [this](std::filesystem::path new_file_path) {
// Postpone `on_file_changed` call until `FileLoadView` is closed
nav_.set_on_pop([this, new_file_path]() {
on_file_changed(new_file_path);
button_send_stop.focus();
draw_waveform();
});
};
};
// Configure save to ook file button
button_save.on_select = [this, &nav](const ui::Button&) {
outputFileBuffer = "";
text_prompt(
nav,
outputFileBuffer,
64,
[this](std::string& buffer) {
on_save_file(buffer);
});
};
// clean out loaded file name if field is changed
field_symbol_rate.on_change = [this](int32_t) {
text_app_status.set(""); // Clear loaded file text field
};
// clean out loaded file name if field is changed
field_repeat.on_change = [this](int32_t) {
text_app_status.set(""); // Clear loaded file text field
};
// clean out loaded file name if field is changed
field_pause_symbol_duration.on_change = [this](int32_t) {
text_app_status.set(""); // Clear loaded file text field
};
// clean out loaded file name if field is changed
field_sample_rate.on_change = [this](size_t, int32_t) {
text_app_status.set(""); // Clear loaded file text field
};
// setting up FrequencyField
field_frequency.set_value(ook_editor_tx_freq);
// clean out loaded file name if field is changed, save ook_editor_tx_freq
field_frequency.on_change = [this](rf::Frequency f) {
ook_editor_tx_freq = f;
text_app_status.set(""); // Clear loaded file text field
};
// allow typing frequency number
field_frequency.on_edit = [this]() {
auto freq_view = nav_.push<FrequencyKeypadView>(field_frequency.value());
freq_view->on_changed = [this](rf::Frequency f) {
field_frequency.set_value(f);
text_app_status.set(""); // Clear loaded file text field
};
};
// allow different steps on symbol_rate and pause_symbol_duration
field_step.on_change = [this](size_t, int32_t value) {
text_app_status.set(""); // Clear loaded file text field
field_symbol_rate.set_step(value);
field_pause_symbol_duration.set_step(value);
};
// Configure button to manually set payload through text input
button_set.on_select = [this, &nav](Button&) {
text_prompt(
nav,
ook_data.payload,
100,
[this](std::string& s) {
text_payload.set(s);
draw_waveform();
text_app_status.set(""); // Clear loaded file text field
});
};
// Configure button to start or stop the transmission
button_send_stop.on_select = [this](Button&) {
if (!is_transmitting) {
update_ook_data_from_app();
start_tx(); // Begin transmission
} else {
stop_tx();
}
};
// initial waveform drawing (should be a single line)
draw_waveform();
}
} // namespace ui::external_app::ook_editor
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
*
* This file is part of PortaPack.
*
*/
#ifndef __UI_OOK_EDITOR_H__
#define __UI_OOK_EDITOR_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_freq_field.hpp"
#include "ui_textentry.hpp"
#include "ui_fileman.hpp"
#include "ui_transmitter.hpp"
#include "radio_state.hpp"
#include "app_settings.hpp"
#include "file_path.hpp"
#include "ook_file.hpp"
using namespace ui;
namespace ui::external_app::ook_editor {
#define PADDING_LEFT 1 // waveform padding
#define PADDING_RIGHT 1 // waveform padding
#define OOK_SAMPLERATE_DEFAULT 2280000U // Set the default Sample Rate
#define TRANSMISSION_FREQUENCY_DEFAULT 433920000U // Sets the default transmission frequency (27 MHz).
#define WAVEFORM_BUFFER_SIZE 550
class OOKEditorAppView : public View {
public:
void focus() override;
OOKEditorAppView(NavigationView& nav);
~OOKEditorAppView();
std::string title() const override {
return "OOKEditor";
};
private:
NavigationView& nav_; // Reference to the navigation system.
uint32_t progress = 0; // Stores the current transmission progress.
int16_t waveform_buffer[WAVEFORM_BUFFER_SIZE]; // Buffer for waveform data.
bool is_transmitting = false; // State of transmission.
rf::Frequency ook_editor_tx_freq{24000000}; // last used transmit frequency
std::string outputFileBuffer{}; // buffer for output file
ook_file_data ook_data = {0, 0, 0, 0, 0, ""}; // ook files handle
// draw current payload waveform
void draw_waveform();
// update ook_data from GUI selection
void update_ook_data_from_app();
// start transmission
void start_tx();
// stop transmission
void stop_tx();
// Updates the transmission progress on the progress bar.
void on_tx_progress(const uint32_t progress, const bool done);
// Updates data when a new file is loaded.
void on_file_changed(const std::filesystem::path& new_file_path);
// Prepare a new std::filesystem::path from string value and call saveFile
void on_save_file(const std::string value);
// Called by on_save_file to save the current OOK parameters to the file
bool save_ook_to_file(const std::filesystem::path& path);
// Registers a message handler for transmission progress updates.
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress, // Transmission progress message ID.
[this](const Message* const p) { // Callback to handle the message.
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.progress, message.done);
}};
// Sets transmission frequency, bandwidth, and default sample rate for OOK.
TxRadioState radio_state_{TRANSMISSION_FREQUENCY_DEFAULT, 1750000, OOK_SAMPLERATE_DEFAULT};
// Settings manager for app configuration.
app_settings::SettingsManager settings_{
"ook_editor",
app_settings::Mode::TX,
{{"ook_editor_tx_freq"sv, &ook_editor_tx_freq}}};
// UI components for frequency and transmitter view.
FrequencyField field_frequency{{0 * 8, 0 * 16}};
TransmitterView2 tx_view{{20 * 7, 0 * 16}, true};
// Labels for various fields such as sample rate and repeat count.
Labels label_step{{{170, 20}, "Step:", Theme::getInstance()->fg_light->foreground}};
Labels label_sample_rate{{{0, 20}, "SampleRate:", Theme::getInstance()->fg_light->foreground}};
Labels label_symbol_rate{{{0, 40}, "SymbolRate:", Theme::getInstance()->fg_light->foreground}};
Labels label_symbol_rate_unit{{{132, 40}, "/s", Theme::getInstance()->fg_light->foreground}};
Labels label_repeat{{{154, 40}, "Repeat:", Theme::getInstance()->fg_light->foreground}};
Labels label_pause_symbol_duration{{{0, 60}, "PauseSymbol:", Theme::getInstance()->fg_light->foreground}};
Labels label_pause_symbol_duration_unit{{{132, 60}, "us", Theme::getInstance()->fg_light->foreground}};
Labels label_payload{{{0, 80}, "Payload:", Theme::getInstance()->fg_light->foreground}};
Labels label_waveform{{{0, 188}, "Waveform:", Theme::getInstance()->fg_light->foreground}};
// Text field to display the various status message of the app
Text text_app_status{{0, 160, 30 * 8, 16}, ""};
// OptionsField for selectable sample rates.
OptionsField field_sample_rate{{96, 20}, 7, {{"250k", 250000U}, {"1M", 1000000U}, {"2M", 2000000U}, {"5M", 5000000U}, {"10M", 10000000U}, {"20M", 20000000U}}};
// OptionsField for step symbol rates.
OptionsField field_step{{210, 20}, 7, {{"1", 1}, {"10", 10}, {"100", 100}}};
// Number fields for symbols, pause_symbol between repeat, and repeat count.
NumberField field_symbol_rate{{96, 40}, 4, {0, 9999}, 1, '0', false};
NumberField field_pause_symbol_duration{{96, 60}, 4, {0, 9999}, 1, '0', false};
NumberField field_repeat{{210, 40}, 3, {1, 999}, 1, '0', false};
// Text field to display the payload data.
Text text_payload{{0 * 8, 100, 30 * 8, 16}, ""};
// Buttons for setting configurations, opening files, and starting transmission.
Button button_set{{0, 125, 60, 28}, LanguageHelper::currentMessages[LANG_SET]};
Button button_open{{68, 125, 80, 28}, LanguageHelper::currentMessages[LANG_OPEN_FILE]};
Button button_save{{154, 125, 80, 28}, LanguageHelper::currentMessages[LANG_SAVE_FILE]};
Button button_send_stop{{80, 273, 80, 32}, LanguageHelper::currentMessages[LANG_SEND]};
// Progress bar to display transmission progress.
ProgressBar progressbar{{2 * 8, 250, 208, 16}};
// Waveform display using waveform buffer and yellow theme color.
Waveform waveform{{0, 208, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
};
}; // namespace ui::external_app::ook_editor
#endif /*__UI_OOK_EDITOR_H__*/
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_ookbrute.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::ookbrute {
void initialize_app(ui::NavigationView& nav) {
nav.push<OOKBruteView>();
}
} // namespace ui::external_app::ookbrute
extern "C" {
__attribute__((section(".external_app.app_ookbrute.application_information"), used)) application_information_t _application_information_ookbrute = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::ookbrute::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "OOKBrute",
/*.bitmap_data = */ {
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0xE0,
0x07,
0xF0,
0x0F,
0x30,
0x0C,
0x30,
0x0C,
0xF0,
0x0F,
0xF0,
0x0F,
0x70,
0x0D,
0xB0,
0x0E,
0x70,
0x0D,
0xB0,
0x0E,
0xF0,
0x0F,
0xE0,
0x07,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+253
View File
@@ -0,0 +1,253 @@
/*
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_ookbrute.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace ui;
namespace ui::external_app::ookbrute {
void OOKBruteView::focus() {
button_startstop.focus();
}
OOKBruteView::OOKBruteView(NavigationView& nav)
: nav_{nav} {
add_children({
&button_startstop,
&field_frequency,
&tx_view,
&options_atkmode,
&field_start,
&field_stop,
});
button_startstop.on_select = [this](Button&) {
if (is_running) {
is_running = false;
stop();
} else {
is_running = true;
start();
}
};
options_atkmode.on_change = [this](size_t, int32_t i) {
update_start_stop(i);
validate_start_stop();
};
field_start.on_change = [this](int32_t) {
validate_start_stop();
};
field_stop.on_change = [this](int32_t) {
validate_start_stop();
};
update_start_stop(0);
}
void OOKBruteView::update_start_stop(uint32_t proto) {
uint8_t bits = 12;
switch (proto) {
default:
case 0:
case 2:
case 4:
bits = 12;
break;
case 1:
case 3:
case 5:
bits = 24;
break;
}
uint32_t max = (1 << bits) - 1;
field_start.set_range(0, max);
field_stop.set_range(0, max);
field_start.set_value(0);
field_stop.set_value(max);
}
void OOKBruteView::validate_start_stop() {
if (field_start.value() > field_stop.value()) {
field_start.set_value(field_stop.value());
}
if (field_stop.value() < field_start.value()) {
field_stop.set_value(field_start.value());
}
}
void OOKBruteView::generate_packet() {
uint32_t protocol = options_atkmode.selected_index_value();
uint8_t byte = 0;
size_t bitstream_length = 0;
uint8_t* bitstream = shared_memory.bb_data.data; // max 512 in size
uint32_t samples_per_bit = 0; // OOK_SAMPLERATE * bit_duration_in_sec
std::string dataFormat = "";
std::string zero = "";
std::string one = "";
uint16_t databits = 0;
uint16_t repeat = 1;
uint16_t pause_sym = 0;
if (protocol == 0) { // came 12
samples_per_bit = OOK_SAMPLERATE / ((3 * 1000) / 1);
dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
databits = 12;
zero = "011";
one = "001";
repeat = 2;
pause_sym = 0;
}
if (protocol == 1) { // came24
samples_per_bit = OOK_SAMPLERATE / ((3 * 1000) / 1);
dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCCCCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
databits = 24;
zero = "011";
one = "001";
repeat = 2;
pause_sym = 0;
}
if (protocol == 2) { // nice12
samples_per_bit = OOK_SAMPLERATE * (680.0 / 1000000.0);
dataFormat = "000000000000000000000000000000000000000001CCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
databits = 12;
zero = "011";
one = "001";
repeat = 2;
pause_sym = 0;
}
if (protocol == 3) { // nice24
samples_per_bit = OOK_SAMPLERATE * (680.0 / 1000000.0);
dataFormat = "000000000000000000000000000000000000000001CCCCCCCCCCCCCCCCCCCCCCCC0000"; // 36 0 preamble +start bit + data
databits = 24;
zero = "011";
one = "001";
repeat = 2;
pause_sym = 0;
}
if (protocol == 4) { // holtek_ht12
samples_per_bit = OOK_SAMPLERATE * (390.0 / 1000000.0);
dataFormat = "0000000000000000000000000000000000001CCCCCCCCCCCC00000000000"; // 36 0 preamble +start bit + data.
databits = 12;
zero = "011";
one = "001";
repeat = 2;
pause_sym = 0;
}
if (protocol == 5) { // princeton24
samples_per_bit = OOK_SAMPLERATE * (450.0 / 1000000.0); // long = 3*
dataFormat = "000000000000000000000000000000000000CCCCCCCCCCCCCCCCCCCCCCCC10000000"; //
databits = 24;
zero = "1000";
one = "1110";
repeat = 6;
pause_sym = 0;
}
std::string fragments = ""; // storage
uint16_t cdb = 0; // current data bit
for (auto c : dataFormat) { // generate fragments from template
if (c == '0') fragments += '0';
if (c == '1') fragments += '1';
if (c == 'C') {
if (counter & (1 << (databits - cdb - 1))) {
fragments += one;
} else {
fragments += zero;
}
cdb++;
}
}
// create bitstream
for (auto c : fragments) {
byte <<= 1;
if (c != '0') byte |= 1;
if ((bitstream_length & 7) == 7)
bitstream[bitstream_length >> 3] = byte;
bitstream_length++;
}
// Finish last byte if needed
size_t padding = 8 - (bitstream_length & 7);
if (padding != 8) {
byte <<= padding;
bitstream[(bitstream_length + padding - 1) >> 3] = byte;
padding++;
}
// send bitstream
baseband::set_ook_data(
bitstream_length,
samples_per_bit,
repeat,
pause_sym,
0);
}
void OOKBruteView::stop() {
transmitter_model.disable();
baseband::shutdown();
button_startstop.set_text(LanguageHelper::currentMessages[LANG_START]);
}
void OOKBruteView::start() {
counter = field_start.value();
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
transmitter_model.enable();
button_startstop.set_text(LanguageHelper::currentMessages[LANG_STOP]);
generate_packet();
}
void OOKBruteView::on_tx_progress(const bool done) {
if (done) {
if (is_running) {
counter++;
field_start.set_value(counter);
if (counter > (uint32_t)field_stop.value()) {
stop();
} else {
generate_packet();
}
}
}
}
OOKBruteView::~OOKBruteView() {
is_running = false;
stop();
}
} // namespace ui::external_app::ookbrute
/*
https://web.archive.org/web/20230331125843/https://phreakerclub.com/447
*/
+106
View File
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2024 HTotoo
*
* This file is part of PortaPack.
*
*/
#ifndef __UI_OOKBRUTE_H__
#define __UI_OOKBRUTE_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 "utility.hpp"
using namespace ui;
namespace ui::external_app::ookbrute {
#define OOK_SAMPLERATE 2280000U
class OOKBruteView : public View {
public:
OOKBruteView(NavigationView& nav);
~OOKBruteView();
void focus() override;
std::string title() const override {
return "OOKBrute";
};
private:
NavigationView& nav_;
TxRadioState radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
TxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
TransmitterView2 tx_view{
{11 * 8, 0 * 16},
/*short_ui*/ true};
app_settings::SettingsManager settings_{
"tx_ookbrute", app_settings::Mode::TX};
Button button_startstop{
{0, 3 * 16, 96, 24},
LanguageHelper::currentMessages[LANG_START]};
NumberField field_start{
{0 * 8, 1 * 16},
8,
{0, 2500},
1,
' ',
true};
NumberField field_stop{
{11 * 8, 1 * 16},
9,
{0, 2500},
1,
' ',
true};
OptionsField options_atkmode{
{0 * 8, 2 * 16},
12,
{{"Came12", 0},
{"Came24", 1},
{"Nice12", 2},
{"Nice24", 3},
{"Holtek12", 4},
{"Princeton24", 5}}};
bool is_running{false};
uint32_t counter = 0; // for packet change
void start();
void stop();
void on_tx_progress(const bool done);
void validate_start_stop();
void update_start_stop(uint32_t proto);
void generate_packet();
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);
}};
};
}; // namespace ui::external_app::ookbrute
#endif /*__UI_OOKBRUTE_H__*/
+1
View File
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_protoview.application_information"), u
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_protoview */ {'P', 'P', 'V', 'W'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
@@ -75,6 +75,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 gullradriel
*
* 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_remote.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::remote {
void initialize_app(ui::NavigationView& nav) {
nav.push<RemoteAppView>();
}
} // namespace ui::external_app::remote
extern "C" {
__attribute__((section(".external_app.app_remote.application_information"), used)) application_information_t _application_information_remote = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::remote::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Remote",
/*.bitmap_data = */ {
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0x20,
0x00,
0xE0,
0x07,
0xF0,
0x0F,
0x30,
0x0C,
0x30,
0x0C,
0xF0,
0x0F,
0xF0,
0x0F,
0x70,
0x0D,
0xB0,
0x0E,
0x70,
0x0D,
0xB0,
0x0E,
0xF0,
0x0F,
0xE0,
0x07,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::HOME,
/*.desired_menu_position = */ 4,
/*.m4_app_tag = portapack::spi_flash::image_tag_replay */ {'P', 'R', 'E', 'P'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -20,7 +20,6 @@
*/
#include "ui_remote.hpp"
#include "binder.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
@@ -34,11 +33,11 @@
#include "utility.hpp"
#include "file_path.hpp"
namespace ui::external_app::remote {
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
static constexpr uint8_t text_edit_max = 30;
/* RemoteEntryModel **************************************/
@@ -309,9 +308,9 @@ void RemoteEntryEditView::load_path(std::filesystem::path&& path) {
entry_.metadata = {transmitter_model.target_frequency(), 500'000};
}
/* RemoteView ********************************************/
/* RemoteAppView ********************************************/
RemoteView::RemoteView(
RemoteAppView::RemoteAppView(
NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_replay);
@@ -365,27 +364,27 @@ RemoteView::RemoteView(
refresh_ui();
}
RemoteView::RemoteView(NavigationView& nav, fs::path path)
: RemoteView(nav) {
RemoteAppView::RemoteAppView(NavigationView& nav, fs::path path)
: RemoteAppView(nav) {
load_remote(std::move(path));
refresh_ui();
}
RemoteView::~RemoteView() {
RemoteAppView::~RemoteAppView() {
stop();
baseband::shutdown();
save_remote(/*show_error*/ false);
}
void RemoteView::focus() {
void RemoteAppView::focus() {
if (model_.entries.empty())
button_add.focus();
else
buttons_[0]->focus();
}
void RemoteView::create_buttons() {
void RemoteAppView::create_buttons() {
// Handler callbacks.
auto handle_send = [this](RemoteButton& btn) {
if (btn.entry()->path.empty())
@@ -420,7 +419,7 @@ void RemoteView::create_buttons() {
}
}
void RemoteView::reset_buttons() {
void RemoteAppView::reset_buttons() {
// Whever the model's entries instance is invalidated,
// all the pointers in the buttons will end up dangling.
// TODO: This is pretty lame. Could maybe static alloc?
@@ -428,7 +427,7 @@ void RemoteView::reset_buttons() {
btn->set_entry(nullptr);
}
void RemoteView::refresh_ui() {
void RemoteAppView::refresh_ui() {
field_title.set_text(model_.name);
field_filename.set_text(remote_path_.stem().string());
@@ -441,7 +440,7 @@ void RemoteView::refresh_ui() {
}
}
void RemoteView::add_button() {
void RemoteAppView::add_button() {
if (model_.entries.size() >= max_buttons)
return;
@@ -454,7 +453,7 @@ void RemoteView::add_button() {
set_needs_save();
}
void RemoteView::edit_button(RemoteButton& btn) {
void RemoteAppView::edit_button(RemoteButton& btn) {
// Don't let replay thread read the model while editing.
stop();
@@ -471,7 +470,7 @@ void RemoteView::edit_button(RemoteButton& btn) {
};
}
void RemoteView::send_button(RemoteButton& btn) {
void RemoteAppView::send_button(RemoteButton& btn) {
// TODO: If this is called while is_sending() == true,
// it just stops and doesn't start the new button?
@@ -510,14 +509,14 @@ void RemoteView::send_button(RemoteButton& btn) {
});
}
void RemoteView::stop() {
void RemoteAppView::stop() {
// This terminates the underlying chThread.
replay_thread_.reset();
transmitter_model.disable();
ready_signal_ = false;
}
void RemoteView::new_remote() {
void RemoteAppView::new_remote() {
save_remote();
init_remote();
refresh_ui();
@@ -526,7 +525,7 @@ void RemoteView::new_remote() {
set_dirty();
}
void RemoteView::open_remote() {
void RemoteAppView::open_remote() {
auto open_view = nav_.push<FileLoadView>(".REM");
open_view->push_dir(remotes_dir);
open_view->on_changed = [this](fs::path path) {
@@ -536,7 +535,7 @@ void RemoteView::open_remote() {
};
}
void RemoteView::init_remote() {
void RemoteAppView::init_remote() {
model_ = {"<Unnamed Remote>", {}};
reset_buttons();
set_remote_path(next_filename_matching_pattern(remotes_dir / u"REMOTE_????.REM"));
@@ -546,14 +545,14 @@ void RemoteView::init_remote() {
show_error("Couldn't make new remote file.");
}
bool RemoteView::load_remote(fs::path&& path) {
bool RemoteAppView::load_remote(fs::path&& path) {
set_remote_path(std::move(path));
set_needs_save(false);
reset_buttons();
return model_.load(remote_path_);
}
void RemoteView::save_remote(bool show_errors) {
void RemoteAppView::save_remote(bool show_errors) {
if (!needs_save_)
return;
@@ -564,7 +563,7 @@ void RemoteView::save_remote(bool show_errors) {
set_needs_save(false);
}
void RemoteView::rename_remote(const std::string& new_name) {
void RemoteAppView::rename_remote(const std::string& new_name) {
auto folder = remote_path_.parent_path();
auto ext = remote_path_.extension();
auto new_path = folder / new_name + ext;
@@ -581,7 +580,7 @@ void RemoteView::rename_remote(const std::string& new_name) {
set_remote_path(std::move(new_path));
}
void RemoteView::handle_replay_thread_done(uint32_t return_code) {
void RemoteAppView::handle_replay_thread_done(uint32_t return_code) {
if (return_code == ReplayThread::END_OF_FILE) {
if (check_loop.value() && current_btn_) {
send_button(*current_btn_);
@@ -598,15 +597,15 @@ void RemoteView::handle_replay_thread_done(uint32_t return_code) {
stop();
}
void RemoteView::set_remote_path(fs::path&& path) {
void RemoteAppView::set_remote_path(fs::path&& path) {
// Unfortunately, have to keep these two in sync because
// settings doesn't know about fs::path.
remote_path_ = std::move(path);
settings_.remote_path = remote_path_.string();
}
void RemoteView::show_error(const std::string& msg) const {
void RemoteAppView::show_error(const std::string& msg) const {
nav_.display_modal("Error", msg);
}
} /* namespace ui */
} // namespace ui::external_app::remote
@@ -41,7 +41,7 @@
#include <string_view>
#include <vector>
namespace ui {
namespace ui::external_app::remote {
/* Maps icon index to bitmap. */
class RemoteIcons {
@@ -84,7 +84,7 @@ class RemoteIcons {
&bitmap_icon_sonde,
&bitmap_icon_stealth,
&bitmap_icon_tetra,
&bitmap_icon_temperature};
&bitmap_icon_peripherals_details};
};
// TODO: Use RGB colors instead?
@@ -257,14 +257,14 @@ class RemoteEntryEditView : public View {
};
/* App that allows for buttons to be bound to captures for playback. */
class RemoteView : public View {
class RemoteAppView : public View {
public:
RemoteView(NavigationView& nav);
RemoteView(NavigationView& nav, std::filesystem::path path);
~RemoteView();
RemoteAppView(NavigationView& nav);
RemoteAppView(NavigationView& nav, std::filesystem::path path);
~RemoteAppView();
RemoteView(const RemoteView&) = delete;
RemoteView& operator=(const RemoteView&) = delete;
RemoteAppView(const RemoteAppView&) = delete;
RemoteAppView& operator=(const RemoteAppView&) = delete;
std::string title() const override { return "Remote"; };
void focus() override;
@@ -385,4 +385,4 @@ class RemoteView : public View {
}};
};
} /* namespace ui */
} // namespace ui::external_app::remote
@@ -0,0 +1,66 @@
// RocketGod's Shopping Cart Lock app
// https://betaskynet.com
#include "ui.hpp"
#include "shoppingcart_lock.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::shoppingcart_lock {
void initialize_app(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
nav.push<ShoppingCartLock>();
}
} // namespace ui::external_app::shoppingcart_lock
extern "C" {
__attribute__((section(".external_app.app_shoppingcart_lock.application_information"), used)) application_information_t _application_information_shoppingcart_lock = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::shoppingcart_lock::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Cart Lock",
/*.bitmap_data = */ {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x7E,
0x7E,
0x00,
0x00,
0x00,
0x00,
0x00,
0x7E,
0x81,
0x81,
0x7E,
0x00,
0x00,
0x00,
0x7E,
0x81,
0x81,
0x81,
0x81,
0x7E,
0x00,
},
/*.icon_color = */ ui::Color::red().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'T', 'X'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,235 @@
// RocketGod's Shopping Cart Lock app
// https://betaskynet.com
#include "shoppingcart_lock.hpp"
using namespace portapack;
namespace ui::external_app::shoppingcart_lock {
void ShoppingCartLock::log_event(const std::string& message) {
static const size_t MAX_LOG_LINES = 50;
static std::vector<std::string> message_history;
message_history.push_back(message);
if (message_history.size() > MAX_LOG_LINES) {
message_history.erase(message_history.begin());
menu_view.clear();
for (const auto& msg : message_history) {
menu_view.add_item({msg,
ui::Theme::getInstance()->fg_green->foreground,
nullptr,
[](KeyEvent) {}});
}
} else {
menu_view.add_item({message,
ui::Theme::getInstance()->fg_green->foreground,
nullptr,
[](KeyEvent) {}});
}
menu_view.set_highlighted(menu_view.item_count() - 1);
}
bool ShoppingCartLock::is_active() const {
return (bool)replay_thread;
}
void ShoppingCartLock::focus() {
menu_view.focus();
}
void ShoppingCartLock::stop() {
log_event(">>> STOP_SEQUENCE_START");
if (is_active()) {
log_event("... Resetting Replay Thread");
replay_thread.reset();
}
log_event("... Stopping Audio Output");
audio::output::stop();
log_event("... Resetting State Variables");
transmitter_model.disable();
ready_signal = false;
thread_sync_complete = false;
looping = false;
current_file = "";
log_event("<<< STOP_SEQUENCE_COMPLETE");
}
std::string ShoppingCartLock::list_wav_files() {
log_event(">>> WAV_SCAN_START");
auto reader = std::make_unique<WAVFileReader>();
bool found_lock = false;
bool found_unlock = false;
for (const auto& entry : std::filesystem::directory_iterator(wav_dir, u"*")) {
if (std::filesystem::is_regular_file(entry.status())) {
auto filename = entry.path().filename().string();
std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower);
if (filename == shoppingcart_lock_file || filename == shoppingcart_unlock_file) {
std::string file_path = (wav_dir / filename).string();
if (reader->open(file_path)) {
if (filename == shoppingcart_lock_file) {
found_lock = true;
log_event("... Found: " + shoppingcart_lock_file);
log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
log_event("Channels: " + std::to_string(reader->channels()));
log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
}
if (filename == shoppingcart_unlock_file) {
found_unlock = true;
log_event("... Found: " + shoppingcart_unlock_file);
log_event("Sample Rate: " + std::to_string(reader->sample_rate()));
log_event("Channels: " + std::to_string(reader->channels()));
log_event("Bits/Sample: " + std::to_string(reader->bits_per_sample()));
}
}
}
if (found_lock && found_unlock) {
break;
}
}
}
if (!found_lock || !found_unlock) {
log_event("!!! Missing Required Files:");
if (!found_lock) log_event("!!! Missing: " + shoppingcart_lock_file);
if (!found_unlock) log_event("!!! Missing: " + shoppingcart_unlock_file);
menu_view.hidden(true);
text_empty.hidden(false);
} else {
log_event("... All Required Files Found");
menu_view.hidden(false);
text_empty.hidden(true);
}
log_event("<<< WAV_SCAN_COMPLETE");
return found_lock && found_unlock ? "Required WAV files found" : "Missing required WAV files";
}
void ShoppingCartLock::wait_for_thread() {
uint32_t timeout = 100;
while (!ready_signal && timeout > 0) {
chThdYield();
timeout--;
}
}
void ShoppingCartLock::restart_playback() {
auto reader = std::make_unique<WAVFileReader>();
std::string file_path = (wav_dir / current_file).string();
if (!reader->open(file_path)) return;
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
BUFFER_SIZE,
NUM_BUFFERS,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
log_event(">> SENDING <<");
audio::output::start();
transmitter_model.enable();
}
void ShoppingCartLock::play_audio(const std::string& filename, bool loop) {
auto reader = std::make_unique<WAVFileReader>();
stop();
std::string file_path = (wav_dir / filename).string();
if (!reader->open(file_path)) {
nav_.display_modal("Error", "Cannot open " + filename);
return;
}
const uint32_t wav_sample_rate = reader->sample_rate();
const uint16_t wav_bits_per_sample = reader->bits_per_sample();
current_file = filename;
looping = loop;
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
BUFFER_SIZE,
NUM_BUFFERS,
&ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
wait_for_thread();
log_event("... Configuring Baseband");
const uint32_t bb_sample_rate = 1536000;
const uint32_t decimation = bb_sample_rate / wav_sample_rate;
baseband::set_audiotx_config(
bb_sample_rate / decimation,
0.0f,
5.0f,
wav_bits_per_sample,
wav_bits_per_sample,
0,
true,
false,
false,
false);
baseband::set_sample_rate(wav_sample_rate);
log_event("... Starting Audio Output");
audio::output::start();
log_event("... Setting Max Volume");
audio::headphone::set_volume(audio::headphone::volume_range().max);
transmitter_model.enable();
log_event(">>> Playback Started <<<");
}
ShoppingCartLock::ShoppingCartLock(NavigationView& nav)
: nav_{nav} {
add_children({&menu_view,
&text_empty,
&button_lock,
&button_unlock,
&button_stop});
button_lock.on_select = [this](Button&) {
if (is_active()) stop();
log_event(">>> LOCK_SEQUENCE_START");
play_audio(shoppingcart_lock_file, true);
};
button_unlock.on_select = [this](Button&) {
if (is_active()) stop();
log_event(">>> UNLOCK_SEQUENCE_START");
play_audio(shoppingcart_unlock_file, true);
};
button_stop.on_select = [this](Button&) {
log_event(">>> STOPPING AUDIO");
stop();
};
list_wav_files();
log_event("[+] INITIALIZATION COMPLETE");
log_event("[+] PORTAPACK ARMED");
log_event("[*] STATUS: READY");
}
ShoppingCartLock::~ShoppingCartLock() {
stop();
baseband::shutdown();
}
} // namespace ui::external_app::shoppingcart_lock

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