Compare commits

...

472 Commits

Author SHA1 Message Date
sommermoregentraum ac1d350aaf add a item that warn user to put ext app if nothing to pop in home page (#1798)
add an item that warn user to put ext app if nothing to pop
2024-01-22 03:21:06 +08:00
Mark Thompson 5f8e1ef307 Daylight Savings Time support (#1793)
* Daylight Savings Time support

* Cleanup

* Clean-up

* Revert ADSB change

* Clean-up

* Corrected date in comment, ironically
2024-01-21 12:47:28 -06:00
E.T aa5d4ad078 Update github workflow to remove deprecated set-output usage (#1799) 2024-01-22 07:24:38 +13:00
sommermoregentraum 44d9572f5c add_bitmap_reverse_decode (#1795) 2024-01-21 18:51:08 +08:00
sommermoregentraum 3314001205 bring keyfob app back (#1794)
* bring keyfob app back

* format

* cleanup

* format

* remove committed line from original list
2024-01-21 11:49:17 +01:00
E.T ce1084abc7 Introduce common app list for menu and serial (#1791) 2024-01-20 23:35:39 +01:00
E.T ea8563cb19 Fix workflow to pick up the OCI named ppfw (#1787) 2024-01-18 21:08:02 +01:00
Mark Thompson b3c1c83677 Remove time from PPFW filename and modify Flash app to support .tar (#1783)
* Update timestamp in PPFW filename

* Only include today's DATE in filename

* Remove date from PPFW filename

* Allow any .tar file name in FIRMWARE folder

* Add files via upload

* Removed unnecessary lines per ufoka
2024-01-18 13:59:44 -06:00
Totoo fcbc3b4d75 pmemreset and settingsreset commands (#1786) 2024-01-18 16:12:13 +01:00
Mark Thompson 39e1a0ffe8 Cmake - generate new ppfw when firmware changes (#1782) 2024-01-17 07:01:48 +01:00
Mark Thompson 99bbd8805c Added Settings app to disable Config Mode (#1781) 2024-01-17 06:59:35 +01:00
Mark Thompson e7af85ffba Add new pmem fields to Debug Dump (#1780) 2024-01-16 22:49:56 +01:00
Mark Thompson 75f3277102 Geomap zoom-out fixes (#1777) 2024-01-16 18:47:31 +01:00
Totoo 216f8b64bf Fix build after merge (#1776) 2024-01-16 10:05:59 +01:00
Mark Thompson bc301c5fdb Geomap enhancements (#1767)
* Increase Geomap zoom-in ability
* Use floating point for current position
* Show grid squares when zoomed in too much for map
* Zoom in fast after exceeding map resolution & clean up redundant code
* Revert order of functions to make it easier to review
* Changed grid color for better contrast with markers
* Optimizations
* Set x_pos/x_pos to center pixel versus upper left corner
* Show more distant planes when zoomed out
* Correct pixel offset when zooming in
* Fix oops in x_pos/y_pos centering change
* Wrapping support for lat/lon fields
* Wrapping support (for Geomap lat/lon fields)
* Handle wrapping for negative lat/lon
2024-01-16 09:30:31 +01:00
gullradriel ae9d6de093 added sysinfo and radioinfo (#1769)
* added sysinfo and radioinfo

* using fillObuffer instead of chprintf

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2024-01-15 15:54:27 +13:00
Bernd Herzog 7a4c3184a1 reworked usb serial communication (#1766) 2024-01-13 18:05:29 +01:00
Mark Thompson 58307aee9c Make use of "ensure_directory" function (#1765) 2024-01-13 07:57:32 +01:00
Bernd Herzog 409242507c Usb serial fix queue full crash (#1763)
* fixed usb serial queue crashing

* fixed usb input buffer handling

* fixed black screen issue
2024-01-12 21:49:10 +01:00
E.T 0a8194fa10 Really go home before starting app from serial (#1762) 2024-01-12 17:33:41 +01:00
Bernd Herzog eaac40df3a fixed usb serial queue crashing (#1761) 2024-01-12 16:04:32 +01:00
Mark Thompson 58b488b840 Added new "App Setting" app (#1759)
* New "App Settings" app for quicker settings access
2024-01-12 13:41:13 +01:00
Totoo 1676702a09 Shows a message when flashing from serial (#1758) 2024-01-12 10:30:24 +13:00
Totoo 3943848add GPS + orientation for Sonde (#1757)
* RadioSonde has gpsdata
* Orientation update in Sonde
2024-01-11 22:17:06 +01:00
Totoo 831dbeaab5 Add extra info from serial (#1754)
* Inject GPS postition from serial
2024-01-11 17:46:35 +01:00
jLynx 49e719ded8 Update repo links (#1750)
* Update repo links to new link

* Removed travis

* Removed travis

* Updated more links
2024-01-10 21:13:43 +13:00
Mark Thompson c2d2409bff Modify encoder handler to not call "on_encoder" functions if no change (#1749) 2024-01-09 23:32:58 -06:00
Mark Thompson 485f57d86b Added Audio Out to Soundboard app (#1748) 2024-01-09 13:50:45 -06:00
Totoo 0e6f303eec fix buffer size bug (#1747)
* fix buffer size bug

* faster file over usbcdc - wip

* minimal faster display tx
2024-01-10 06:30:43 +13:00
Totoo ad94ae05c1 Fwrite fix (#1744) 2024-01-09 09:09:25 +13:00
Bernd Herzog 89dc8c0463 found missing revert (#1743) 2024-01-09 08:13:51 +13:00
Bernd Herzog 77640c5cc0 Usb serial filesystem improvements (#1742)
* refactoring

* improved file system commands

* added binary read/write
2024-01-08 19:53:40 +01:00
Erwin Ried cdc8cfeb7d Update README.md (#1740) 2024-01-08 22:45:46 +13:00
Bernd Herzog 4740df2e2c Usb serial shell workerthread #2 (#1738) 2024-01-07 22:25:43 +01:00
Bernd Herzog ec0f45a488 added sys locks to time critical lcd updates (#1737) 2024-01-07 19:50:26 +01:00
Totoo 9d7e06c255 Appstart and applist from serial (#1736)
* Start app, and list fixed ones.
* Add ext app support
2024-01-07 18:56:09 +01:00
sommermoregentraum 4b93e78dd9 make pacman's face (mouse) face to the dir which he hits the wall (#1735) 2024-01-07 16:55:45 +01:00
Bernd Herzog 5ced8c90d3 added radio saturation to performance counter (#1734) 2024-01-07 15:31:08 +01:00
Bernd Herzog 9d22711368 added usb event to not wait for the next frame (#1733) 2024-01-07 14:09:22 +01:00
Bernd Herzog 23e6295dd2 removed the use of the hackrf cpld eeprom (#1732)
* removed the use of the hackrf cpld eeprom

* readded comment
2024-01-07 12:29:53 +01:00
Bernd Herzog bbd75ab7ef fixed usb descriptor product string length (#1731) 2024-01-07 11:57:16 +01:00
jLynx 7508fee62c Fixed sdcard zip folder structure (#1730)
* Fixed sd card zip folder structure

* Update create_nightly_release.yml

Updated zip for work map

* Update create_stable_release.yml

Replicated on stable release
2024-01-07 19:51:27 +13:00
John Stockdale fe88760766 First version of rtcget and rtcset (#1727) 2024-01-06 09:15:59 +01:00
E.T 75ece38725 Dockerfile overhaul (#1726)
Create entrypoint to orchestrate the build steps
Supported commands: make, ninja
Passes additional arguments to the make / ninja command at the end (like -jNN)
There is a shortcut to make -jNN by just specifying -jNN
Anything else will be directly executed (like getting a shell into the container with bash -li is still possible)
2024-01-06 00:42:36 +01:00
E.T b53032a8a5 Ppfw docker build (#1725)
* Docker build ppfw by default

* Allow GH workflows to be triggered from any branch

* Fix asset path for github upload
2024-01-05 21:59:15 +01:00
Mark Thompson e7f8952ece Fix compile error in deprecated app (#1723) 2024-01-05 10:13:43 -06:00
Totoo 82a6ae0791 Geomap speed display (#1722)
* speed to geomap
* Add speed to map
* Fix hidden state
* UI fix on ADSB tx
* UI fixes
2024-01-05 13:44:30 +01:00
Totoo d303098e35 Moved SPainter and GPSSim to ext app (#1721)
* Moved GPSSim
* Moved spainter
2024-01-05 10:46:09 +01:00
Totoo 1a69ce2d97 Accessibility over serial (#1717)
* Initial accessibility support
* added it to some widgets to test
* More widget accessibility response
* More widgets, better output
* Mark selected widget on list
* typo
2024-01-05 07:43:30 +01:00
Erwin Ried eedebe1c52 Update your links (#1719) 2024-01-05 07:43:13 +01:00
E.T 9e61f80809 Create cmake target ppfw (#1716)
* Create cmake target ppfw and oci

It will build an OCI ppfw package to be flashed or shared on test-drive

Additionally some minor cmake cleanup
And updated gitignore to allow multiple build folders

* Make ppfw / oci build default
2024-01-04 22:59:25 +01:00
Mark Thompson ff591c68a0 Updated Flash message to say device will restart (#1715) 2024-01-04 11:47:45 -06:00
Totoo 8761b9d7e0 Send ASCII chars from USB serial to selected widget (#1708)
* Initial commit for keyboard emulation
* Added on_keyboard to some widgets
* TextEdit partly
* Multi key send at once
* Frequency control support
* Fix encoder emulation
* Add keyboard to geomap
* More widgets
2024-01-04 17:36:31 +01:00
Erwin Ried 1b5125b0a8 Adding the new TechMinds video (#1712)
I think it explains very well everything, even if he calls it "Portapak"
2024-01-04 11:28:33 +01:00
E.T 9ba885361d add _OCI to ppfw package name (#1711) 2024-01-04 20:52:42 +13:00
Mark Thompson afa34d83b2 Added Zoom-Out capability to Geomap (#1710) 2024-01-04 07:32:44 +01:00
E.T d93c914dd3 Create firmware ppfw.tar to flash firmware together with apps (#1709)
* Github actions test

* Action to create and upload ppfw.tar

+remove unused travis config

* Changelog token fix

* Also generate ppfw for stable releases

* Make tar verbose
2024-01-03 22:32:34 +01:00
Mark Thompson 715a2dd448 Add Tamper Flags to ERT RX display and meter ID to log file (#1707) 2024-01-03 20:17:33 +01:00
Totoo 58bf60695d Touch emulation from usb cdc (#1706) 2024-01-03 14:06:29 +01:00
Totoo fbe7954f2e Added support for extracting firmware from TAR file (with apps) (#1704)
* Added support for extracting firmware from TAR file (with apps)
* Added tar to usb cdc firmware command.
* Serial flash tar
* Show error on bad tar
* Check tar for valid filenames
2024-01-03 14:05:24 +01:00
tomvandermussele d122a8fc3b Created Belgian Airband file (#1705)
* Created file in FREQMAN for the Belgian Airband (Brussels Airport)

* Added Military to BE Airband
2024-01-03 12:02:04 +01:00
Bernd Herzog 3998dc124a added cpld info shell commands (#1703)
* added cpld info shell commands

* fixed reset
2024-01-02 00:18:53 +01:00
jLynx 3d2da9c0db Serial info command (#1700)
* WIP adding more info

* Added source output & R9 detection

* WIP

* WIP

* Moved hackrf back

* Fixed output error
2024-01-02 07:25:36 +13:00
Erwin Ried 3189d3af4c Update FUNDING.yml (#1702)
Removing bountysource, since they scammed everyone: https://github.com/bountysource/core/issues/1586
2024-01-01 15:14:37 +01:00
E.T 1b3a99cc5e Gcc warning onscreen (#1701)
* Fix GCC version detection for old CMAKE

* Yellow tint version number / hash on gcc mismatch
2023-12-31 15:46:26 -06:00
E.T 935c43e88f Gcc version warning (#1698)
* Compiler warning
* Fix warning message
2023-12-31 16:36:19 +01:00
gullradriel 2893c031ab Recon repeat delay (#1699)
- Recon repeat delay, range: from 0s to 254s, timer is played before each TX
- fixed missing '<' '>' around button names for DELETE and REMOVE name to indicate that they can also be used to skip in the list
- added missing pmem recon dump entries
2023-12-30 22:32:41 +01:00
E.T 94cdb16ca9 Mention external apps in stable release notes (#1694) 2023-12-29 15:11:40 -06:00
jLynx 84d4066986 V1.9.1 (#1691)
* Update past_version.txt

* Update version.txt
2023-12-29 22:07:43 +13:00
Mark Thompson 2153c2dd10 ERT app - Fix freq field overlapping amp setting (#1689) 2023-12-29 07:28:42 +01:00
gullradriel a7393c3492 fix for freq_b equal zero in repeater entries (#1686) 2023-12-28 20:10:38 +01:00
gullradriel 794fece8cc Recon raw repeat (#1658)
* UI for Recon Repeater config
* persistent recon repeat settings
* record_view: added possibility to force keep the provided record filename
* working auto record to same file
* Recon TX 
* added Repeater type
* adding yellow coloring on Repeater config+a modal, comments in the code
* default repeater values
2023-12-28 11:25:53 +01:00
Totoo 1bf95e85a0 Fixed #1670 (#1676) 2023-12-26 07:16:54 +01:00
Brumi-2021 a85357a8af Minor Improvement (Extending_min_CLK_out_from_10khz_to_4khz) (#1675) 2023-12-26 07:11:14 +01:00
Totoo 7bf3e02f6c Faster usb serial file download (#1674)
* Made file read much faster via USB serial
* Remove old code
2023-12-25 17:25:16 +01:00
Mark Thompson 459e8d0b24 Disallow CLKOUT freq changes on r9 (#1672) 2023-12-24 17:28:22 +01:00
Mark Thompson 93585d846a CLKOUT workaround for r9 boards (#1671) 2023-12-24 11:06:11 +01:00
Mark Thompson 4eb5c4603e Simplify CLKOUT frequency entry (#1668) 2023-12-24 08:55:15 +01:00
Mark Thompson f42ad5ffc6 Configure CLKOUT at power-up per PMEM setting (#1669) 2023-12-24 08:54:56 +01:00
gullradriel d0bd8e50be Added USB serial checkbox (#1664) 2023-12-22 11:00:27 +01:00
Mark Thompson 337e3f4449 Enhance Debug->Peripherals to view all registers of SI5351 chip (#1663) 2023-12-22 08:45:05 +01:00
jLynx 53bbf74f4d V1.9.0 (#1660)
* Update version.txt

* Update past_version.txt
2023-12-22 07:35:54 +13:00
Mark Thompson 19a66fac20 Enable Sample Rate to be spec'd in .ini file for Weather & SubGhzDecoder apps (#1659) 2023-12-21 19:33:06 +01:00
Totoo 36e1b9a36e Portapack USB screen tx (#1653)
* implemented 2 screen read functions, and haxfixed windows usb bug
* Support up to 64 byte per packet for faster tx
* Added filesize command
2023-12-19 22:25:22 +01:00
Netro 1ca9288bbe initial USB serial thread integrated to BLE RX (#1651) 2023-12-19 22:20:13 +01:00
Mark Thompson 4ca2d1e352 Fix compile error (#1650) 2023-12-18 07:35:00 +01:00
gullradriel 0b11730835 fix wrong direction in tx freq correction (#1649)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-12-17 18:47:20 -06:00
Bernd Herzog 6069145b68 Usb serial (#1648)
* enabled usb clock
* added usb stack to application
* fixed pll0usb clock setup
* implemented serial usb handshake
* implemented serial communication
* integrated chibios shell
* implemented device reset
* implemented enter dfu mode
* implemented hackrf mode command
* implemented flash command
* implemented memory manipulation
* implemented button control
* fixed mode change
* improved reset behavior
* implemented directory commands
* implemented file commands
* improved data communication
* refactorings
2023-12-17 17:20:35 +01:00
Totoo 2ccda5aebd Subghz decoder (#1646)
* Initial commit - wip

* Half part of the transition of baseband processor.

* More SGD

* WIP, Weather refactor, UI improv

* Rename

* Added 4msps, and fixes

* Fixes

* princeton working

* Renamed proc_weather, bc now multifunctional

* Proto: bett

* FPS_CAME = 4,
    FPS_PRASTEL = 5,
    FPS_AIRFORCE = 6,

* Came Atomo, fixes

* Separate weather and sgd, bc of baseband size limit

* Fix display

* Save space

* More protos

* Dooya proto added

* More protos

* add protos

* More protos

* Move weather to ext app

* nw

* Revert "Move weather to ext app"

This reverts commit 8a84aac2f5.

* revert

* Fix merge

* Better naming

* More protos

* More protos

* Add protos

* Fix warning

* Add NeroRadio

* more protos

* more protos

* More protos

* Shrink a bit

* fixes

* More protos

* Nicer code

* Fix naming

* Fix format

* Remove unused

* Fix some protos, that needs a LOOOONG part with the same lo/high

* Modify key calculation
2023-12-16 16:37:51 -06:00
Totoo 02810bf527 Move Jammer ext app (with baseband image too) (#1647) 2023-12-15 20:18:21 +01:00
Totoo b58ee761a1 Move more apps + language module (#1643)
* CoasterP to ext (Burger pager)
* LGE to ext app
* Solve compiler literal removal with centralizing common string literals.
2023-12-12 16:55:50 +01:00
Totoo 5b9d898202 Fix oregon2 weather protocol (#1642) 2023-12-12 06:45:56 +01:00
Totoo 5f8e44e7b7 NRF RX to ext app (#1641)
* NFR Rx to ext app
2023-12-11 11:57:04 +01:00
Totoo ec80859d93 AnalogTV to ext (#1638) 2023-12-11 08:55:07 +01:00
Totoo 5e9d92d65c Set sr to 4m (#1636)
* Set sr to 4m

* Rename variable
2023-12-10 23:40:11 +01:00
Mark Thompson c3bb9c0a16 Weather display beautification (#1635) 2023-12-10 17:57:21 +01:00
Netro bd475e6f9c BLE RX - Find From File Initial Commit. (#1627)
* initial work
* Handling capping count only at total count
* Adding logging back in
2023-12-09 18:02:00 +01:00
Mark Thompson 0f1708d8fd Increase SSP timing margin for MAX2839 registers (#1632) 2023-12-09 14:09:20 +01:00
Mark Thompson efa0e270ad Updated Weather & TPMS app icons (#1630) 2023-12-09 09:25:41 +01:00
Totoo ff882024d4 Ble spam improvements (#1626)
* Much faster ble  spam tx
* Better Android handling
2023-12-09 09:24:02 +01:00
Brumi-2021 60d95447fb Improved sensitivity/selectivity Weather App (#1628) 2023-12-08 21:33:48 +01:00
Mark Thompson a474bf8fe3 Add files via upload (#1629) 2023-12-08 19:20:59 +01:00
Totoo 1bf0d2b3e6 BLE spam ext app (#1624)
* BLESpam ext app first release

* Ios partly added

* Fix missing!

* Maybe fix Ios

* Add comment

* Fixes

* Added samsung and windows

* cleaning a bit

* Partial size fix

* Move continuity to blespam namespace.

* Optimize code layout, so not using fw space

* Ui changes
2023-12-06 08:35:23 -05:00
Mark Thompson ea02d98a59 Preserve BLE RX app settings across runs, and changed BLE app settings file names (#1622)
* Add files via upload

* Add files via upload

* Add files via upload
2023-12-04 12:06:07 -05:00
Mark Thompson 9624d7b429 Fix compiler warning (#1620) 2023-12-03 08:19:04 +01:00
Totoo 4f440055cb Fix to_string_hex_array string reserve (#1619) 2023-12-02 16:42:09 -06:00
Netro 7354ba8fd5 Random data selection feature done. (#1617) 2023-12-02 08:05:29 +01:00
Netro b7b4a10485 Refined Tx Random Data (Still WIP) (#1616)
* managing initial cursor
* work on adding marked cursors
2023-11-30 21:57:22 +01:00
Totoo cca0e18f5a Weather improvements (#1615)
* Added Acurite986 protocol
* Added signal age
* Added myself to about screen
2023-11-30 12:36:59 +01:00
Netro 8846926b68 BLE TX: use text viewer to inject rand data WIP (#1613) 2023-11-29 18:54:17 +01:00
Mark Thompson 7ceefb1b81 Weather app tweaks (#1610)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-11-29 10:37:01 -06:00
Erwin Ried 861f684d00 Discord icon/invite more clear on top (#1612) 2023-11-29 13:22:51 +01:00
Erwin Ried 17b146b960 Update README.md (#1611) 2023-11-29 11:23:27 +01:00
Totoo 02251eeeb4 Added weather station app with 18 protocol parsers (#1607)
* Added weather station app with 18 protocol parsers
* Fix button and formatting
* Set BW to 1.75m, changed to us in dsp part
2023-11-28 21:11:30 +01:00
Netro c486572d3d BLE TX; Added feature to randomize found substring in data (#1609) 2023-11-28 21:11:07 +01:00
Netro a6ed6e3099 BLE RX: Making auto channel timer independent of packet events. (#1608) 2023-11-28 18:18:15 +01:00
Netro 175e5e2e8c BLE: Minor improvements to fix occasional not start on tx app first time. (#1605) 2023-11-27 21:59:29 +01:00
Mark Thompson 7bc27038be Save Morse app settings (#1602) 2023-11-26 12:17:23 +01:00
Brumi-2021 609bf3219f solve Morse_TX_App latch up in r9 (#1601) 2023-11-25 01:01:52 +01:00
Mark Thompson bdbd616a84 added a frequency field to the ERT app 2023-11-24 09:25:50 +01:00
Mark Thompson 1cbe5cb293 Save TPMS units in App Settings and tune frequency (#1598) 2023-11-21 18:44:10 +01:00
gullradriel df6003c08f fix button label (#1597)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-20 12:09:47 -05:00
Netro 50fcbf0f5a BLE: added deleting and renaming temp file (#1596) 2023-11-20 08:37:56 +01:00
Netro 5a1676a0b9 BLE List improvements (#1595)
* WIP on add to save list

* Added rewriting existing lines and appending new to same file.

* format

* not looking on new file.

* Added temp list due to some issue of recent being updated during save

I was receiving a system halt and this seems to fix it. Needs more testing though.

* clang format

* Stored entries backward. Fix.

* Update file working, need to replace temp file though.
2023-11-19 20:53:53 -05:00
gullradriel 309f2fbd2c support for a sdcard speed option (#1594)
* support for a sdcard speed option

* trying to link sdio_cclk_set from file

* changed io to IO in checkbox text

* changed order so high speed option is read after pmem is restored from sd

* test button

* took out unneeded comment

* force behavior of test button

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-19 08:08:29 -06:00
gullradriel 650aacfaa7 BLE RX: added clear list button (#1593)
* added clear list button

* Bigger buttons and text

* Change case on labels

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-18 16:32:52 -05:00
Mark Thompson 8655027a5b Fixed SPI Read timing issue affecting MAX283x chips (#1590)
* Slowed SPI timing to MAX283x by 20%

* Fix temperature scaling
2023-11-18 09:50:57 -06:00
Mark Thompson de937f02d4 Added Memory Dump app for Debug (#1588)
* Add files via upload

* Add files via upload

* Added single word read/write support

* Add files via upload
2023-11-18 07:16:56 -06:00
Mark Thompson 2c0446e83d Improved Focus Manager navigation (#1589)
* Focus manager navigation fix

* Add files via upload
2023-11-18 06:58:38 -06:00
Netro 9e6699d8cc Fixed name. (#1586) 2023-11-17 13:28:09 -05:00
Mark Thompson ef92c5bc0a Enhanced Debug -> Peripherals app to allow direct register modification (#1584)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Clang

* Clang

* Tweak position of field on screen

* Reverted text on Write button per reviewer feedback
2023-11-17 12:10:11 -06:00
Netro 10f6600f52 No need limit to 50 since we moved to min 16ms send times. (#1583) 2023-11-16 21:28:22 +01:00
Netro aeb607e73b BLE Rx Save List / Save packet from BLE Rx (#1582)
* Added Tx Save from Rx Details View
* Added saving of current list
2023-11-16 20:00:52 +01:00
Netro 8479d2edf0 BLE Comm WIP (#1578)
* Initial BLE Comm commit.
* SCAN_RSP MAC was reversed.
* Added Auto Channel Hop.
* Improvements to Tx to better handle timers.
* Auto channel and more work on timers.
* more advertisement numbers.
2023-11-16 14:46:45 +01:00
Bernd Herzog d2e9a8dc06 Hackrf mode when no pp attached (#1580)
* added portapack presence detection

* adjusted timeout

* fixed ak4951 initialisation
2023-11-14 12:22:26 +01:00
Netro 4571cb0b61 BLE Rx Refactoring / Cleanup (#1577) , also added showing MAC for Scan_Rsp Adv_Direct 2023-11-12 19:10:53 +01:00
gullradriel 351f7b13b3 BTLE RX: fix for name disappearing (#1575)
* Fix for name disappearing

* cleanings

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-12 09:59:36 -05:00
Netro 5953cb57b0 Rx parsing fix (#1573)
* restored mistakenly quoted code
* removed unneeded line.
* added parsing for other PDU Types and added PDU Type label.
* added missing SCAN_IND
2023-11-12 08:54:04 +01:00
Brumi-2021 b60b873428 Changing BLTE FIR filter to improve RX sensitivity (#1574) 2023-11-11 20:43:10 -05:00
Netro a3249cae26 BLE Rx/Tx App Cleanup (#1570)
* Showing highest dB first
* Including namestring in search
* Using replace seems to work better.
* bletx cleanup
* removing pop
* pop is needed on this view.
* cleanup switching
* reduce to 1 entry list
* Setting to use the name of BLE with a toggle checkbox.
* Removed &nav reference.
* Removing const
* Fixed issue with memory being run out when switching from a previous app
* Fixed not setting updated progressbar for each new packet.
2023-11-11 20:46:51 +01:00
Kyle Reed f7f784c0f4 Cleanup dead code in the ADSB RX proc (#1572)
* Cleanup dead code in the ADSB RX proc

* Fix comments

* Formatting
2023-11-10 11:38:06 -08:00
Kyle Reed f4f538f69b ADSB RX App cleanup (#1569)
* WIP refactoring/gardening

* WIP cleanup, adding status dots

* Rename ageStep

* WIP Cleanup

* Wrapping up ADSB refactor/cleanup.

* Don't initialize strings to "     "

* Better map refresh

* Fix colorization of recent entries

* Fit and finish
2023-11-10 08:22:29 -08:00
Netro bbd1a5a2ef Changing filter on ble rx. Seems to work better? (#1568) 2023-11-08 19:22:57 +01:00
Nemanja Nedeljković bc71238046 Patch build for gcc 13.2.0 (#1567) 2023-11-08 19:22:26 +01:00
Netro a9df9dde69 BLE Rx parsing improvements (#1566)
* Running simple state machine to better catch in between buffers. 
* Fixed buffer size 2048 was too large for a decimated before only containing 512 (2048/4 Decimation) Bytes.
* attempt at trying to minimize uncleared str buffers
2023-11-07 18:10:18 +01:00
Mark Thompson 7a87e3f3af Added File Manager button to show hidden files (#1564)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload
2023-11-06 16:33:19 -06:00
Mark Thompson 68f960e4e7 Temperature scaling changes for HackRF r9 (MAX2839) (#1561)
* Add files via upload

* Add files via upload

* Add files via upload
2023-11-06 13:56:09 -06:00
gullradriel 6784fe72dd fixed display when delete/remove entry (#1563) 2023-11-06 20:38:37 +01:00
Netro 312df0fcec Save tx file (#1560)
* Added saving current packets to file
2023-11-06 19:09:26 +01:00
Erwin Ried 2913a41aec Update README.md (#1559) 2023-11-06 11:16:13 +01:00
Netro adabbfbef1 Send on select (#1557)
* fixed bug not fully displaying rx packet
* added sending selected packet
* setting packet count to 100 as 1 is too aggressive for speed > 2
* setting limit to 50 as 50 does not bog down UI at speed 5.
* fix tx channel getting out of sync with freq
2023-11-06 11:16:01 +01:00
gullradriel 2b7962fa7c Level RSSIGraph tuning (#1556)
* Added whole graph RSSI min/avg/max/delta to RSSIGraph
* fix for min=0 values when modulation is changed
* literal number as a #define flag
2023-11-06 11:12:36 +01:00
gullradriel 645f02e2f3 updated submodule (#1551)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-11-04 22:41:38 -05:00
Netro b178462e47 BLE Rx improvements. (#1550)
* Added parsing for ble name added focus for ble tx play button.
* Added sort by name
* Fixed issue where we were pushing too early before pop was finished.
* Fix ordering
* Fix field location.
* truncating entries to stop memory overflow
* Added Number of Hits.
* Limiting number of hits to uint16_t, as any higher would overflow screen
* Limiting number of hits to uint16_t due to possible screen overflow.
* Stop creating a log every time app starts. Cluttering log folder.
2023-11-04 08:29:48 +01:00
Netro 1639348b94 More btle improvements (#1549)
* Cleanup of BLE Rx naming and added switching between the two.
Fixed bug in packet count not resetting when opening new file in BLE Tx
* Bug fix and color change.
2023-11-03 08:08:49 +01:00
Netro f67fe262bb Add open file focus and changing the way send and loop works (#1548) 2023-11-02 19:47:34 +01:00
Netro 0681117af6 Bletx multi packet (#1547)
* Added multiple packet support
* Fixed Issue where default was RAW and discovery needed toggle to work on init
* Added random MAC toggle
2023-11-02 17:32:29 +01:00
Netro b5c244a09b Ble tx improvements (#1546) 2023-11-01 23:24:55 +01:00
Netro dceb7255b0 BLE Tx App / BLE Rx Filtering. (#1543)
BLE TX app creation
BLE RX and TX app improvements
2023-11-01 11:46:41 +01:00
Brumi-2021 62307b93d7 Clean compile warning (#1545) 2023-11-01 00:25:11 +01:00
Mark Thompson 61ce8b11a6 Mic app enhancements - "Transmitting" icon, PTT TX button always enabled, and persistent settings (#1531)
* Add files via upload

* Clang

* Clang

* Persist mic settings

* Preserve mode setting

* Clang

* Add files via upload

* Add files via upload

* Clang

* Clang

* Adjusted Transmitting icon position

* Clang

* Changed common freq checkbox description

* Fix swizzled USB/LSB/DSB modulation values in widget
2023-10-31 23:31:10 +01:00
gullradriel 211f40ddf3 Recon: fix output when typing name (#1538)
* Changed some buttons name and fixed blank output if typed

* code format

---------

Co-authored-by: Gull Ra Driel <gull@NiloreaDevLaptop.home>
Co-authored-by: gullradriel <gull@nomail.net>
2023-10-30 16:54:37 -05:00
pathetic nihilist 01dd0c79ed tiny fix: fix for wrong wav (#1540)
* fix_for_wrong_wav

* correct_wrong_file
2023-10-30 11:15:09 -05:00
Brumi-2021 be7b402c95 Adding raw data stream BPSK - QPSK (#1534)
* Adding raw data stream BPSK - QPSK
* minor code compact
2023-10-30 11:32:34 +01:00
Mark Thompson a11929d2ca Preallocate enough vectors for common app settings #1535 2023-10-30 11:32:01 +01:00
Mark Thompson 0bacb31d17 Improve blacklist to require precise app name matches (#1532)
* Add files via upload

* Add files via upload

* Add files via upload

* Clang

* Update ui_btngrid.cpp

* Update comment
2023-10-28 21:32:59 +02:00
Mark Thompson cc7e4aec8a Add files via upload (#1533) 2023-10-28 08:28:17 +02:00
Netro 15c338e5b3 Added Sorting to BLE app (#1527)
* First BLE work

* Adding new fsk proc WIP

* Reverting ble stuff

* Initial compile working

* more work.

* Adding waterfall for debug

* more edits to debug

* Work to get widgets to show.

* cleanup before attempting diff fsk modulation method

* Temporary debug to learn how decimation scales.

* Tab view for console and spectrum. Spectrum still not working right.

* Fixed spectrum offset.

* Added audio sampling rate increments to freqman

* Added overriding range for frequency field and working off  deviation

* BLE cleanup. Got PDU parsing.

* Parsing CRC

* forgot :

* Removing AA again because cluttering UI

* fix compile

* attempt at throttling.

* WIP changes.

* Decimating by 4 to handle issue with overloading.

* Attempt to parse MAC still needs work.

* Small fixes. MAC still wrong.

* Fixed invalid indexing on Symbols.

* List view of BLE Mac Addresses

* Added Channel Option and improved GUI header.

* renaming to dB and fixing some warnings.

* Advertisements only.

* Initial cut of BLE Advertisement scan app.

* Copyrights

* formatting correctly in association to clang13

* Fixing warning and hiding fsk rx.

* spacing

* Removing some cmake install files that weren't suppose to be there.

* missed some.

* Added name to about.

* Edits for PR review pt.1

* Refactor ORing with 0 doesn't make sense.

* remove  parenthesis

* More PR Review changes.

* Fix compiler error.

* PR Review edits.

* PR review changes.

* Fixes.

* Unneeded ;

* Update ui_about_simple.cpp

* Disabling frequency stepping.

* removing unused label

* Fixing something that got messed up in merge?

* removing file that got added.

* editing labels.

* Added sort functionality.

* removed unused code.

---------

Co-authored-by: jLynx <admin@jlynx.net>
2023-10-24 17:08:45 -05:00
Netro 80bfcd5eb1 Restricting Frequency change to only Channel Options Toggle. (#1526)
* First BLE work
2023-10-23 20:54:04 +02:00
Netro b96f14762f New and Improved BLE App. (#1524)
* First BLE work

* Adding new fsk proc WIP

* Reverting ble stuff

* Initial compile working

* more work.

* Adding waterfall for debug

* more edits to debug

* Work to get widgets to show.

* cleanup before attempting diff fsk modulation method

* Temporary debug to learn how decimation scales.

* Tab view for console and spectrum. Spectrum still not working right.

* Fixed spectrum offset.

* Added audio sampling rate increments to freqman

* Added overriding range for frequency field and working off  deviation

* BLE cleanup. Got PDU parsing.

* Parsing CRC

* forgot :

* Removing AA again because cluttering UI

* fix compile

* attempt at throttling.

* WIP changes.

* Decimating by 4 to handle issue with overloading.

* Attempt to parse MAC still needs work.

* Small fixes. MAC still wrong.

* Fixed invalid indexing on Symbols.

* List view of BLE Mac Addresses

* Added Channel Option and improved GUI header.

* renaming to dB and fixing some warnings.

* Advertisements only.

* Initial cut of BLE Advertisement scan app.

* Copyrights

* formatting correctly in association to clang13

* Fixing warning and hiding fsk rx.

* spacing

* Removing some cmake install files that weren't suppose to be there.

* missed some.

* Added name to about.

* Edits for PR review pt.1

* Refactor ORing with 0 doesn't make sense.

* remove  parenthesis

* More PR Review changes.

* Fix compiler error.

* PR Review edits.

* PR review changes.

* Fixes.

* Unneeded ;

* Update ui_about_simple.cpp

---------

Co-authored-by: jLynx <admin@jlynx.net>
2023-10-22 22:58:14 -07:00
Brumi-2021 0feacfa102 Solve mic to hp small side effect (#1523)
* Allowing to hear Mic at TX time with Receiver ON

* Solve small side effect to Audio RX App
2023-10-22 19:10:26 +02:00
Brumi-2021 a1496c1b79 Allowing to hear Mic at TX time with Receiver ON (#1522) 2023-10-22 01:13:32 +02:00
Brumi-2021 1b73a138b6 Mic to hp refactoring (#1521)
* Refactoring previous "Hear Mic" PR

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

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

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

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

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

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

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

* Add files via upload

* Add files via upload

* Add files via upload

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

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

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

* Added trim icon

* Added trim icon

* Added Trim button to FileMan

* Added Trim button to FileMan

* Added Trim button to FileMan

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

* improved output

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

* added some ui stuff for testing

* added pacman game

* wired key to pacman game

* fixed pacman drawing issue

* changed afsk rx app to be external

* fixed ui::NavigationView initialization for external apps

* refactoring

* refactoring

* moved m4 image to external app

* added script for external app deployment

* refactoring

* implemented dynamic app listing

* added color to app icon

* improved app loading

* added external apps to sd card content

* refactoring

* review findings

* typo

* review findings

* improved memory management of bitmaps
2023-10-02 20:19:22 +02:00
Kyle Reed 78713cc2af Search cleanup, more binder support (#1467) 2023-10-01 09:04:37 -07:00
Kyle Reed 951890eaff Add Binder helper methods (#1465)
* Add binder, fix focus on remote btn delete
* Use binder for freqman edit
2023-09-29 18:55:27 +02:00
Kyle Reed fb00bfac3f Nav stack cleanup (#1460)
* Minor nav stack fixes
* Nav stack cleanup
* Additional cleanup, fix notepad crash
* Fix abort/cancel
* Fix for nasty focus bug
* Format
2023-09-27 21:03:02 +02:00
Kyle Reed a6a1483083 Improved trimming (#1458)
* Add threshold UI
* WIP Better trimming
* Rewrite mostly done WIP
* WIP - trim idea
* WIP threshold trimming
* WIP with new design
* Cleanup
2023-09-23 21:56:37 +02:00
Kyle Reed ef03f020ce IQ Trim app and Capture auto-trim (#1456)
* Add IQ Trim app WIP
* Add 'trim' checkbox to capture
* Implement trimming
* Finish auto-trim
* Stray //
2023-09-21 17:43:10 +02:00
Kyle Reed 070eb4003e Remove proc pocsag & add keep/drop filtering (#1453)
* Remove proc_pocsag from build, use new proc in app
* Add support for keep/drop filtering
2023-09-19 11:46:30 +02:00
Kyle Reed fca373d936 Add Remote App & UI updates. (#1451)
* Alpha order sub-menus
* WIP Getting Remote types outlined
* WIP building UI
* WIP adding RemoteButton control
* WIP Fix build
* WIP Basic editing support
* Border on the active button
* Make TxView2 sane
* Add easier RGB color creation from uint32
* Center some button icons
* WIP Remote - main UI
* WIP main UI mostly working, can send
* Add 'join' utility
* WIP save/load
* Pre-alloc buttons to prevent focus dangling
* Alpha order settings/debug pages
* Add UI for picking capture and set frequency
* WIP Getting really close now
* Fix path for init name
* Some fit & finish
2023-09-18 23:22:46 +02:00
Kyle Reed 537cf2e79b Fix console scroll - comment on how scrolling works (#1448)
* Force console scroll area to be multiple of line height. Tons of comments.
* Resize POCSAG console to fill height.
* Make scoll behavior comment clearer
2023-09-14 20:20:40 +02:00
Kyle Reed af424aa5f8 SymField rewrite (#1444)
* First WIP symfield

* Cleanup widget code

* Rebase and format

* Fix 'to_integer' bug, fix siggen UI.

* to_string_hex fix, unit tests for string code

* Pass instance in callback

* Fix on_change callbacks

* Fix keyfob (not active)

* to_byte_array, coaster tx cleanup

* Add to_byte_array tests

* Changes in ui_numbers

* Fix ui_encoders

* Format

* Fix modemsetup view's symfields

* Remove header

* Format
2023-09-12 12:38:19 -07:00
Kyle Reed 70e0f2913f App Settings w/out radio settings, Settings UI cleanup (#1443)
* Make app settings more consistent

* Allow app settings to always work for custom settings
2023-09-10 17:04:20 -07:00
Kyle Reed b28283271b POCSAG clock detection 🐋 improvement(?) (#1442)
* WIP convergence
* Tighter code, allow for sample nudges during clock discovery.
2023-09-09 17:49:22 +02:00
Kyle Reed 4926cf8df5 POCSAG2 Revised bit extractor (#1439)
* Better bit extraction WIP
* Parallel clock signal detection for bit extraction
* Relax clock detection accuracy.
* Reset RateInfo state. TODOs
2023-09-09 15:18:42 +02:00
Kyle Reed b3312a704a Save 'raw log' setting (#1438) 2023-09-08 21:20:26 +02:00
Kyle Reed 31e8019642 POCSAG Processor Rewrite (#1437)
* WIP Refactoring
* WordExtractor building
* Fix buffer sizes and squelch execute
* Move impls to cpp file
* Baud indicator
* WIP new bit extractor
* New approach for bit extraction.
* Code fit and finish
* Fix case on button
* Cleanup
* Adjust rate miss threshold
* Fix count bits error calculation.
2023-09-08 19:41:09 +02:00
Bernd Herzog 9525738118 Config mode hackrf r9 disable external tcxo fallback (#1434)
* implemented auto disable of external tcxo if r9 crash during boot
2023-09-05 10:32:44 +02:00
Bernd Herzog 62310ad9a9 Config mode (#1433)
* Allow code to run without a token
* Reverted submodule change by mistake
* WIP force tcxo
* Added check to hide if not an R9
* Fixed comments
* Updated name
* Fixed crashing on TCXO switching
* Fixed the reboot issue
* Updated boot order
* Cleaned up comments
* added new recovery mode
* added IO
* implemented cpld mode change
* whitespace change
* renamed config mode
* inverted logic
* removed r9 dependency
* added disable external tcxo option to config mode
* fixed CLKIN detection for r9
* integrated tcxo setting into clock selection
Co-authored-by @jLynx
2023-09-04 17:46:07 +02:00
Kyle Reed 4819a2f4e2 Decode status widget (#1431)
* Initial cleanup of pocsag beta, using DSP filters

* Better filter params

* Better filter

* Add signal diagnostics widgets

* POCSAG procs sends stats messages

* Only draw 32 bits

* Add AudioNormalizer filter
2023-09-03 21:49:44 -07:00
Brumi-2021 2435ee780f Small fine tuning xOVS selection for BW 25k (#1430)
* Small fine tuning xOVS selection for BW 25k

* Comments , same growing order ending with  dot.
2023-09-02 00:35:43 +02:00
Kyle Reed 5d602ece5c Add second pocsag proc for experimenting (#1428) 2023-08-31 08:05:49 +02:00
Mark Thompson 900086c1c9 Fixed 0.1Hz error in tone key frequencies 146.2, 156.7, and 162.2 (#1427)
* Fixed tone key freq truncation in float->int conversion
* Round integer division when displaying tone key freq
2023-08-31 08:04:47 +02:00
Kyle Reed 07be74701f POCSAG Squelch and Filtering (#1424)
* Disable audio processing when not using squelch
* Add 2k4 lowpass fitler for 24Khz audio for POCSAG
2023-08-30 19:24:35 +02:00
Kyle Reed f46e20c977 Explain and clean up decimator scalars (#1422) 2023-08-30 09:05:49 -07:00
Kyle Reed 4bc752b7a8 Support file count on sub-folders (#1423) 2023-08-30 09:05:35 -07:00
Brumi-2021 c28b608146 Minor issue : Slow down scrolling speed in BW 1.5Mhz onwards , and adding some TODO comments. (#1419)
* Slow down scrolling speed in BW 1.5Mhz onwards
* Revert added comments
2023-08-30 17:13:40 +02:00
Kyle Reed bd4948602a Fix RSSI for apps using proc_capture (#1420)
* Fix RSSI for apps using proc_capture
* Update comment
2023-08-30 17:13:14 +02:00
Mark Thompson 4774b3097f Support for displaying stack on screen after a fault (for debug) (#1416)
* Support for displaying stack on screen after a fault

* Update debug.cpp

* Highlight possible addresses on stack & support Up key

* Add small background white style

* Add small background white style

* Update debug.cpp

* Highlight fault PC in yellow

* Add bg_yellow_small style

* Add bg_yellow_small style

* Clang - trailing space
2023-08-29 23:26:58 -05:00
Kyle Reed de81156223 Support for 1.25MHz capture (#1418)
* Advanced draft decim /4 just waterfall ok

* apply some Kall's corrections + formatting

* Tidy up both decim_factors

* New refine optimizations

* Format issues

* more format issues ...mmmm

* comments update

* WIP Cleanup

* WIP

* WIP - add variant

* Use std::visit to dispatch MultiDecimator -- fluent API

* Clean up comments

* Merge next and fix compilation

* Fix odd loop in BlockDecimator

* Clean up spectrum math

* Descibe spectrum update math better, more clear math.

* Apply spectrum interval correction at 1.5M

* Increase replay buffer to handle x4 ovs

---------

Co-authored-by: Brumi-2021 <ea3hqj@gmail.com>
2023-08-29 19:26:38 +02:00
Kyle Reed e7e1bedcad Support squelch in pocsag (#1415)
* Support squelch in pocsag

* Revert smooth threshold
2023-08-27 15:56:40 -07:00
Mark Thompson d8930db8af Add Stack Dump option to debug menu and to GURU meditation fault (#1414)
* Stack dump

* Stack dump

* Stack dump

* Stack dump

* Stack dump

* Stack dump

* Update debug.cpp

* Clang

* Update debug.cpp

* Skip dumping unused bytes of stack
2023-08-27 00:33:27 -05:00
Kyle Reed 014db9e233 Option to hide address only messages (#1413) 2023-08-26 19:32:02 -07:00
Kyle Reed 933920edfd POCSAG State machine fix (#1410)
* Reset color for well-formed message fragments

* better colors

* Fix POGSAG decode state machine

* Invert is_message to make more clear

* Use new escape string constants

* Run ECC twice, better diagnostics

* center status icon

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-08-26 13:43:34 -05:00
Mark Thompson cf25d85d51 Declare escape strings for colored text (#1409)
* Color strings

* Color strings

* Color strings

* Color strings

* Color strings

* Color strings & fix first group scrolling off screen

* Color strings

* Color strings

* Clang

* Clang

* Clang attempt #3

* Update ui_painter.cpp

* Update ui_widget.cpp

* Clang test

* Clang

* Clang test

* Update ui_about_simple.cpp

* Update lge_app.cpp
2023-08-25 20:01:37 -05:00
Kyle Reed 9af1308e29 Pocsag UX Revamp (#1408)
* Set bandwidth_filter in pogsac

* WIP pocsag UI revamp

* Settings UI, better console, UI Revamp

* Add baud rate to error messages

* Reset last_addr in error cases

* Show malformed messages as different color vs hiding

* Use light grey to indicate low quality decode

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-08-25 14:45:02 -07:00
Mark Thompson f4496d8f45 Enable lto optimization for application code only (#1405)
Enabling for tonight's nightly build.  (Haven't heard of any issues yet.)
2023-08-24 11:14:57 -05:00
Mark Thompson 966d1c938b Automatically switch to C8 capture format when bandwidth >1.5MHz (#1404)
* Automatically select C8

* Automatically select C8
2023-08-24 10:14:18 +02:00
Kyle Reed f537c7896e Scanner persisted freq file, TextField for current item (#1403)
* Don't truncate string passed to Text widget

* Focus TextField on touch like other fields

* TextField for current, save last opened freq file
2023-08-23 11:51:28 -07:00
Kyle Reed 4a1479957c More settings and cleanup (#1402) 2023-08-22 12:09:59 -07:00
Kyle Reed dc9a16c54b Looking Glass - persist range/preset settings, UI tweaks (#1401)
* WIP

* Add encoder support for TextField

* Working settings, use TextField

* Remove unneeded blanking rectangle
2023-08-22 07:56:10 -07:00
Kyle Reed a476647d70 Don't use raw new/delete (#1398)
* Use unique_ptr in ui_btngrid
* Use unique_ptr for ui_menu
* Use unique_ptr for rssi_dma
* Use unique_ptr for painter
2023-08-21 10:17:23 +02:00
Mark Thompson 95a48e5693 Store search range in settings file (#1397)
* MAX_UFREQ
* Use settings file for search range
2023-08-21 10:16:49 +02:00
Mark Thompson 09404ca198 Default to AK4951 speaker disabled (#1396) 2023-08-20 22:28:23 +02:00
Kyle Reed 564f76b47d Use new settings API in recon and scanner (#1394)
* Use new settings in recon
* Trim on string value read, remove dupe entry
* Check update_range flag when setting values
* Add a few saved settings to scanner
* Add copywrite note
2023-08-20 22:28:02 +02:00
Mark Thompson c6424f1623 Display degree symbol in TPMS, Sonde, and Temperature apps; disabled Font Viewer (#1388)
* Display degrees symbol; disable Font Viewer app to save ROM space
2023-08-20 11:14:22 +02:00
Mark Thompson a4325ac20d Disable audio by default at power-up (#1393) 2023-08-20 11:13:04 +02:00
Kyle Reed d8a6422d37 Consolidate old and new style app settings (#1391)
* Consolidate old and new style app settings

* Remove unused ctor
2023-08-19 09:02:26 -07:00
gullradriel cc963c3562 Recon: Only update manual field if current entry is a range (#1387) 2023-08-18 22:05:28 +02:00
Kyle Reed 63f99742fc First pass at custom app-settings support (#1381)
* First draft of custom app settings support.

* WIP new settings

* Working per-app custom settings

* Revert design to use "bound settings"
2023-08-18 12:35:41 -07:00
Brumi-2021 a4636d7872 Finalise all low bit rate Capture App with x64 (#1386)
* Finalise all low bit rate Capture App with x64

* Adding back also 25k BW option
2023-08-18 14:49:22 +02:00
gullradriel bd2ee03e44 Restoring part of old timer, fixed sd loading (#1385) 2023-08-17 17:28:12 +02:00
Mark Thompson deeb81c183 Correct text field width (#1383) 2023-08-17 00:45:24 +02:00
Brumi-2021 12dbf957b3 Extend REC low bit rate bandwith options in Capture App till 75khz (#1380)
* working_x32_Capture_till_75Khz

* format issues

* apply better accurate comments

* auto format issues
2023-08-16 23:26:59 +02:00
Mark Thompson e1cc0b1ad0 Show file count in each directory, and moved "Too many files" message (#1376)
* Added file_count() function
* Show file count in each directory; moved "Too many files!" warning
2023-08-16 10:00:46 +02:00
gullradriel f079d57fc6 Recon: fixed behavior of auto update m-ranges (#1374) and coloring
* Fixed behavior of auto update m-ranges, added min and max to sd load and save if option not used
* changing xor swap by std::swap
* Fixed consecutive green color
2023-08-15 18:29:16 +02:00
Brumi-2021 09b9ed642f avoid yellow icon in Audio App (minor issue) (#1372)
* avoid yellow icon in Audio App

* clear readability

* just more clear parentesis
2023-08-13 16:52:54 -05:00
Mark Thompson e74a9f3b41 Limit text string length to fit Text widget rectangle (#1370)
* Limit string length to fit Text rectangle

* Update ui_widget.cpp
2023-08-13 20:34:46 +02:00
Mark Thompson ff7a9d10cb Load App Settings *after* initializing RadioState (#1371) 2023-08-13 20:33:31 +02:00
Brumi-2021 5cfd7f1dc2 Small error correction to PR 1367 (#1368) 2023-08-12 10:57:16 -05:00
Brumi-2021 853ca2ef53 Solve_quality_problem_low_bit_rate_100k_150k_REC_Capture_App (#1367)
* Solve_low_bit_rate_150k_Capture_App

* Applying  review comments.

* format issues

* Adding back requested previous low bit rates
2023-08-12 09:20:15 -05:00
Brumi-2021 cb0a4854f5 Increase REC BW Option till 1Mhz Capture App (#1352)
* Increase REC BW Option till 1Mhz Capture App

* Format issues

* miss type correction

* format issues
2023-08-06 21:54:24 +02:00
Mark Thompson 1188157a3e Reject bmp images that aren't 240 pixels wide (#1351) 2023-08-05 17:14:18 +02:00
gullradriel 2ae639412d fix/simplify internal timing (#1348)
* fix/simplify internal timing
2023-08-04 07:56:39 +02:00
Kyle Reed 37386c29cb Oversample (#1336)
* WIP Oversample cleanup

* WIP

* WIP

* WIP dynamic interpolation

* WIP cleanup

* Fix math errors

* Add some optional assertions

* Add support for x32 interpolation

* Update proc_replay.cpp

Typo
2023-08-02 21:59:26 +02:00
jLynx e2ad0a1b1a Stable 1.7.4 release (#1340)
* Update version.txt

* Update past_version.txt
2023-08-02 10:24:23 +12:00
Mark Thompson 96cdb2e9e0 Keyboard tweaks (#1338)
* Disallow 1-time shift in digits/symbols mode

* Slightly wider "123" button and smaller OK button

* Slightly wider "123" button and smaller OK button
2023-08-01 14:25:26 -05:00
Mark Thompson 002ef720ee Fixed waterfall frequency scale in Replay app (#1337)
* Fix waterfall frequency scale in Replay

* Fix waterfall frequency scale in Replay
2023-08-01 20:20:19 +02:00
Kyle Reed 2214533894 Keyboard Shift Mode (#1333)
* Add "shift" concept to keyboard

* Better shift colors

* Better keybaord layout for symbols

* Start ShiftLocked for consistency with previous UX.

* Fix number layout

* PR and test-drive feedback
2023-07-31 21:44:05 -07:00
Mark Thompson 06b7a0419e Fixed Select button responsiveness when updating frequency field during heavy CPU activity (e.g. WFM Audio) (#1335)
* Resolve button responsiveness

* Resolve button responsiveness

* Clang

* Clang

* Clang try again

* Removed unnecessary lines

* Address review comments

* Add comments per reviewer suggestion

* Clang test

* Clang retry
2023-07-31 23:27:15 -05:00
Kyle Reed d24ff7b3bc Oversample capturing for low bandwidths (#1332)
* draft_low_bit_rate_solution_Capture_App

* second_draft_dynamic_decim

* Add support for Oversample Rate to capture.

---------

Co-authored-by: Brumi-2021 <ea3hqj@gmail.com>
2023-07-31 17:46:07 +02:00
Mark Thompson a24b3ad3de Correct estimated capture time in C8 format (#1330)
* Correct capture time remaining for C8 format

* Removed unsupported RawS32 type

* Clang
2023-07-31 08:02:11 -07:00
Mark Thompson 91c6e3fc30 Display error message when trying to delete non-empty directory (#1321)
* Non-empty directory check

* Non-empty directory check

* Display error when attempting to delete non-empty directory

* Clang

Delete white-space that the friggin editor added

* is_empty_directory

* is_empty_directory

* is_empty_directory

* Now need to check if it's a directory first
2023-07-30 15:46:59 -05:00
Kyle Reed 411f6c0a34 Progress bar for Notepad IO (#1322) 2023-07-30 09:36:57 +02:00
Kyle Reed 0a3aa706ef Scanner cleanup (#1320)
* FreqmanDB in Scanner

* Format

* Add comment on squelch(0)
2023-07-29 15:36:00 -07:00
Kyle Reed 5ca74db2f9 Fix output file config in recon (#1319) 2023-07-29 07:16:49 +02:00
Kyle Reed f24523c2f1 Add button to prompt rename with a timestamp. (#1315) 2023-07-28 18:53:16 -07:00
Mark Thompson e7c5a862da Revert encoder sensitivity change (#1314) 2023-07-27 16:45:39 +02:00
Mark Thompson b27c738b69 XOR cursor support in Notepad (#1311)
* XOR cursor support in Notepad

* XOR cursor support

* XOR cursor support

* Revert change

* Use static buffer

* Use static buffer

* Clang
2023-07-27 09:14:02 -05:00
jLynx 8c565bbf15 Allow changelog to run without a token (#1312)
* Allow code to run without a token

* Reverted submodule change by mistake
2023-07-27 14:53:56 +12:00
Mark Thompson 37aa9c046f Gps default frequency (#1309)
* Default frequency

* Default frequency

* Use sample rate from .ini file if present

* Eliminate unneeded sampling_rate variable

* Eliminate unneeded sampling_rate variable
2023-07-26 13:52:27 -05:00
Kyle Reed 195a6224a0 Freq field tweaks (#1306)
* UX improvements

* Prevent wrapping
2023-07-25 08:06:13 -05:00
Mark Thompson ea238f4988 Improve Select button response when Long Press is enabled during CPU-intensive apps (WFM) (#1304)
* Long press

* Long-press improvement during CPU-intensive apps

* Long-press improvement during CPU-intensive apps
2023-07-24 20:45:24 -07:00
Kyle Reed 3514a9a608 Digit Mode for frequency field (#1298)
* Remove 'auto' step mode

* Support per-digit edits on the freq field.

* Swizzle instead of raw accessor

* Fix debug ui after swizzle
2023-07-24 18:09:22 +02:00
Mark Thompson e2bca9aebb Restore missing line (#1302) 2023-07-24 10:12:35 -05:00
Mark Thompson 6ae164e59b Show scale on map (#1296)
* Add scale to geomap

* Add scale to geomap
2023-07-24 14:52:30 +02:00
Mark Thompson e6ad5efbb7 Touch Test tweak & Scanner default squelch (#1295)
* Leave nav bar visible at start & change seed
* Change default Scanner squelch
2023-07-23 21:06:19 +02:00
Kyle Reed 7bd370b5bc Fix baseband thread init order bug for all procs. (#1293) 2023-07-23 08:54:17 +02:00
Mark Thompson 828eb67a52 Remove experimental support for Replaying C32 files (#1294) 2023-07-23 08:52:23 +02:00
Mark Thompson c4df2e66be A Simple Touchscreen Test App (Debug) (#1292)
* Touchscreen test

* Touchscreen test

* Touchscreen test

* Clang

* Moved some Debug menu icons
2023-07-22 16:49:28 -05:00
Kyle Reed 47e95c0c47 Workaround for Capture startup hang (#1285)
* Attempt to fix Capture startup hang

* Pump baseband_queue on M4 startup

* Synchronization experiment

* Moved SpectrumCapture member, better hang detection for M0

* Prevent execute from working on members until class has been initialized.

* Formatting

* Remove workaround.

* Rebase on next
2023-07-22 10:06:55 -07:00
gullradriel 3b5890d0aa fix timer and sd settings issue (#1291) 2023-07-22 16:30:02 +02:00
Mark Thompson bee2dc17af Fixes to C8 capture support (#1290)
* Use divide vs shift in C16->C8 and C32->C16 conversion
* Color cyan for .C32 files
2023-07-22 16:12:35 +02:00
Mark Thompson d6b0173e7a C8 capture support (#1286)
* C8 conversion

* C8 conversion

* C8 support

* C8 support

* C8 support

* C8 support

* Don't auto-convert GPS C8 files

* C8 support

* C8 support

* C8 support

* Remove hang workaround (different PR)

* Comment change

* Clang

* Clang

* Clang

* Merged change from PR #1287

* C8 support

* C8 support

* Improve bandwidth display

* Merged minor optimization from PR 1289

* Merge change from PR 1289

* Use complex types for C8/C16 conversion

* C8 support

* C8 support

* C8 support

* C8 support

* Roll back changes

* Roll back C8 changes

* C8 support

* C8 support

* C8 support

* C8 support

* C8 support

* Don't transmit samples past EOF

* Don't transmit samples past EOF

* Clang

* Clang attempt

* Clang attempt

* C8 support

* Clang
2023-07-22 02:20:56 -05:00
Mark Thompson 8eafe27955 Minor Replay & GPS baseband optimizations (#1289)
* No need to scale progress now that widget is fixed
* Use memcpy vs byte copy, elim progress scaling, correct comments
* Minor optimization in C16->C8 conversion
* Only copy bytes actually read from file
2023-07-21 18:50:00 +02:00
Mark Thompson 2af95456a2 Fix ProgressBar widget overflow (#1288) 2023-07-21 00:06:37 -05:00
Mark Thompson 8ce48dbcf6 Interim Capture hang workaround (#1287)
* Temporary hang workaround

* Clang found a trailing space in a comment :-)
2023-07-20 23:04:54 +02:00
gullradriel 92072b4225 removed redundant call (#1282)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-07-17 15:31:36 -05:00
gullradriel 4e823dd3a3 Recon adjustements (#1280)
* Fix offset by 1 error in button_remove, added back load_freqfile(true) in restart
* removed badly duplicated load freqman file
2023-07-17 22:19:39 +02:00
gullradriel 53d9cecdaf Encoder default setting (#1281)
* fix default encoder value to normal
* use enum name instead of hardcoded value
2023-07-17 22:16:40 +02:00
Mark Thompson 680f1f49ff Resolve compile error (#1279) 2023-07-17 21:31:25 +02:00
Kyle Reed 6574272ca8 Freqman improvements (#1276)
* Show .1 MHz in pretty freqman string

* Refactor load to user FreqmanDB

* Recon file parsing cleanup

* use strtol for parse_int

* recon file cleanup

* Fix bugs in Recon changes

* PR feedback

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-17 11:43:37 -07:00
Brumi-2021 f5c4aa2be2 Correct_RXBW_char_7_spaces_in_Mic_App (#1278)
* Correct_RXBW_char_7_spaces_in_Mic_App

* format_issues_Mic_app

* using_freqman_functions_and_solving_other_defaults
2023-07-17 09:29:24 -05:00
Mark Thompson 2498861003 Increased number of encoder dial sensitivity setting levels (#1275)
* Encoder dial sensitivity 0-4

* Workaround for worn encoders & more sensitivity levels

* Workaround for worn encoders & more sensitivity levels

* Encoder sensitivity levels 0-4

* Encoder sensitivity levels 0-4

* Fix comment

* Clang

* Clang

* Change default encoder sensitivity in pmem

* Set default encoder sensitivity

* Set default encoder sensitivity

* Revert widget type for encoder sensitivity

* Revert widget type for encoder dial sensitivity

* Revert worn encoder workaround (didn't work)

* 5 levels of encoder dial sensitivity

* 5 levels of encoder dial sensitivity
2023-07-16 17:05:27 -05:00
Kyle Reed 25923e82a4 New Freqman edit UI (#1272)
* WIP new edit UI

* Fix textfield highlight

* WIP new edit

* Wrap up first pass of freqman edit

* Fix indexing of options

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-14 18:46:39 -07:00
Mark Thompson 61cb57e48d Added to_string_rounded_freq() function (#1271)
* Added to_string_rounded_freq function

* Added to_string_rounded_freq function

* Clang

* Clang try #2

* Use constexpr for pow10 array

* Additional string_format test cases

* Specify std=c++17 for g++ versions that default to an older standard

* Corrected string_format test cases
2023-07-14 13:38:02 -05:00
Kyle Reed 4985d836ea Support save prompt on back arrow (#1270)
* Support save prompt on back arrow
* Add lifetime warning comment
2023-07-13 18:38:40 +02:00
eried e1a79a0ff1 Fonts for pcb 2023-07-13 15:21:05 +02:00
Kyle Reed d72d935607 Support showing invalid entries in Freqman and allow minor edits (#1269)
* Support showing invalid entries in Freqman and allow minor edits
* Use light_grey instead of grey
* Fix comment
* Fix null in description bug
* Fix spacing in delete entry dialog
* trim in delete modal
2023-07-13 08:28:27 +02:00
Mark Thompson 4ed06b9eff Correct HamRadio frequency validation (#1266) 2023-07-12 20:27:02 +02:00
Mark Thompson fa4623db7c Print leading zeroes after decimal point (#1265) 2023-07-12 13:00:45 -05:00
Mark Thompson a44e8b9921 Use unicode standard 0xA0-0xFF for Latin 1 characters (#1263)
* Update chars 0xA0-0xFF to match unicode

* Update ui_text.hpp

Update chars 0xA0-0xFF to match unicode

* Use unicode 0xB0 for degree character

* Clang
2023-07-12 07:14:00 -05:00
jLynx 6b4f1a552e Changelog refactoring (#1264)
* Delete stable_changelog.py

* Update changelog.py

* Update create_stable_release.yml
2023-07-12 18:50:39 +12:00
pathetic nihilist 3d32682a67 time counting fix (#1261) 2023-07-12 13:10:52 +12:00
Kyle Reed 29e495a17f Freqman UI (#1255)
* FreqmanDB direct file
* Clear UI for short lists
* Final touches on freqlist UI.
* Support vertical alignment in NewButton
* New buttons in FreqMan
* Wiring up UI to filewrapper actions
* Work around empty file
2023-07-11 22:48:36 +02:00
Tragic nihilist 0c599f7d3a Changelog fix 3rd way (#1259)
* init_commit_for_changelog_fix_2nd_way

* 2nd_commit_for_changelog_fix_2nd_way

* methods-3

* add_more_exception
2023-07-11 09:51:22 +02:00
jLynx 006e4c9fce Smaller SD card ZIP (#1257)
* Update create_nightly_release.yml

* Update create_stable_release.yml
2023-07-10 19:43:39 +12:00
Kyle Reed 63be4de418 Add some basic validation to freqman parsing (#1256)
Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-09 11:15:14 -07:00
Kyle Reed 497ca3f934 Refactor freqman_db parsing (#1244)
* WIP freqman changes/memory perf/stash
* Split ui tone_key function out for testing.
* Add more tests and fix bugs.
* Use default max_entries in recond
* Set limit back to 90 for now
2023-07-08 22:04:12 +02:00
Mark Thompson 60de625c37 Added fonts viewer (debug) app (#1251)
* Added fonts viewer (debug) app

* Added fonts viewer (debug) app

* Clang

* Use degree symbol vs asterisk for latitude/longitude degrees

* Save a few bytes by not overriding on_key

* Save a few bytes by not overriding on_key

* Clang
2023-07-08 10:26:05 -05:00
gullradriel a09c0e4c2d Recon and Level fix (#1250)
* Level: Fix for SPEC to other modes
* Recon: Better difference between RAW and AUDIO. Launch Capture instead of audio in SPEC mode
2023-07-07 22:21:48 +02:00
Mark Thompson a60f4ce9a8 Add SPLASH folder to SD Card (#1247)
* Add SPLASH folder and README file

* Sample splash screen files
2023-07-06 23:02:58 +02:00
Mark Thompson fcb681f4ae Support for viewing BMP files in File Manager and setting as Splash screen (#1242)
* Use filesystem::path type vs string for BMP file name

* Use filesystem::path type vs string for BMP files

* Use a global const file path string for "/splash.bmp"

* Support for viewing BMP files and setting as Splash screen

* Support for viewing BMP files and setting as Splash screen

* Support for viewing BMP files and setting as Splash screen

* Update ui_ss_viewer.cpp

* Update ui_ss_viewer.hpp
2023-07-05 15:45:43 -05:00
gullradriel 8530fa8194 Recon unleashed SPEC mode (#1240)
* added quick recon optimized for spec mode
* fix bug going from spec to audio mode
2023-07-05 21:54:28 +02:00
Erwin Ried 89e8956df1 Merge pull request #1239 from gullradriel/recon-gui-fix
fix gui glitches
2023-07-05 12:09:04 +02:00
GullCode 82ba35c162 fix gui glitches 2023-07-05 11:08:29 +02:00
Mark Thompson d750afe38f Use ImageToggle widget for 3 icons (no functional change) (#1237)
* Use ImageToggle button type for 3 icons
2023-07-05 09:12:40 +02:00
Kyle Reed 9b665a43c5 Radio state initialization (#1236)
* WIP RadioState init

* TX/RX cleanup

* Update all apps using RadioState and setting modulation mode

* Set apps to use AM mode

* Don't push modulation update in RadioState.

* Support passing overrides to Audio and MicTX

* Support set_nearest on OptionsField, fix recon step

* Fix audio, typo

---------

Co-authored-by: kallanreed <kylereed@manzana.lan>
Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-04 18:26:26 -05:00
Mark Thompson 80c769b97d Display CTCSS tone freq in Audio, Recon, and Level apps (#1231)
* Generate CTCSS messages at fixed rate regardless of tone freq

* Generate CTCSS messages at fixed rate regardless of tone freq

* Function for generating CTCSS description strings

* Function for generating CTCSS description strings

* Increase width of CTCSS text to include tone freq

* Increase width of CTCSS text field to include frequency

* Use CTCSS tone freq when saving HAM freqs to freq file

* Use function in tone_key.cpp for displaying CTCSS string

* Use function in tone_key.cpp for CTCSS descr strings

* Use function in tone_key.cpp for CTCSS descr strings

* Clang test

* Clang

* Clang

* Support for reading CTCSS tones from FreqMan file

* Clang

* Clean up and eliminate floating point

* Clean up and eliminate floating point

* Corrected CTCSS field length

* Corrected CTCSS field length

* Clang
2023-07-02 16:53:51 -07:00
Kyle Reed 44dd8fd083 Pmem cleanup (#1230)
* WIP pmem cleanup

* Finish pmem refactor

* Fix dump order

* Set defaults for splash and backlight

* Fix break

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-07-02 09:35:34 -07:00
Mark Thompson 4e128e8930 Fixed CTCSS tone detection (#1226)
* Fixed erroneous rounding code

* Fixed CTCSS tone detection

* Correct tone index for XZ/WZ and sort table by tone freq
2023-07-01 15:36:07 +02:00
jLynx c2279e297c Stable v1.7.3 (#1224)
* Update past_version.txt

* Update version.txt
2023-06-30 20:45:20 +12:00
Mark Thompson cdd524b9f3 Use a common function for finding Tone Key index from received Tone Frequency (#1218)
* Common function for finding CTCSS tone index from freq
2023-06-30 07:37:43 +02:00
Kyle Reed 99809c7919 Consolidate anti_alias BW selection into function (#1217) 2023-06-30 07:35:35 +02:00
Kyle Reed 2390d79111 Fix up Waterfall control names (#1219) 2023-06-30 07:34:19 +02:00
Kyle Reed 3b41d73ddd Remove LTO, minor UI tweaks (#1216) 2023-06-30 07:33:33 +02:00
gullradriel 3d46c28911 fix build (#1215)
* fix build

* fixing build integrationg zoom

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-29 13:58:10 -07:00
Kyle Reed ca7840557b Change how scrolling works, add home/end (#1212)
Conflicts fixed by gull
2023-06-29 22:17:31 +02:00
Mark Thompson ceaa115025 Revert color of "SD over USB" icon back to yellow (#1210) 2023-06-29 22:13:33 +02:00
Mark Thompson 8dbcefbc75 Move initializer from .hpp -> .cpp file (#1213) 2023-06-29 22:07:39 +02:00
Mark Thompson b28b96fb4a Notepad text zoom support 5x8 to 8x16 font (#1211)
* Zoom button 5/8 to 8/16 font

* Zoom button 5/8 to 8/16 font

* Clang & revert unintended change

* Suggested improvements

* Suggested code changes

* Added char_width function

* Added char_width function

* Oops copy-paste error

* Delete added blank line
2023-06-29 10:55:25 -07:00
Stupid retard noob attention king e15a8ed2d8 improve keyboard (#1209)
* init commit for raw input in keyboard

* clean up

* clean up - 1

* clean up - 2

* can input underline and < now

* format fix

* textual change

* textual change - 2

* textual change - 3

* textual change - 4

* edit for PR comment

* edit for PR comment - 2

* edit for PR comment - 3

* edit for PR comment - 4

* edit for PR comment - 5
2023-06-29 09:24:18 -07:00
gullradriel 19b77bf03b fix recon start and mode change (#1208) 2023-06-28 22:21:13 +02:00
Kyle Reed 7b1541d2e8 work around calling focus from ctor path. (#1207) 2023-06-28 21:50:11 +02:00
gullradriel 830fea63ed Optimize compilation flags (#1206)
* restoring -O3 on baseband, activating lto on cpp parts
2023-06-28 21:46:49 +02:00
Kyle Reed 3d59de55f8 Remove raw new/delete from Recon (#1203) 2023-06-28 19:14:30 +02:00
Mark Thompson 5c1f5a7d2e Second DFU Overlay Screen with Receiver Info (#1204)
* 2nd DFU overlay with receiver info for debug
2023-06-28 19:04:16 +02:00
Kyle Reed 4e985420d4 Replace Replay with Playlist (#1202)
* Replace Replay with Playlist
* Suggestions from test-drive. Launch from Fileman
2023-06-28 19:02:06 +02:00
Mark Thompson 3e584a9652 Proposed debug dump formatting tweaks (#1200)
* Debug dump formatting 
* Added FW version to debug dump
2023-06-28 19:01:09 +02:00
Mark Thompson 39acaf846a Revised title bar truncation code & removed title widget shading (#1201)
* Revised title truncate & undo widget shading

* Revised title truncation & undo widget shading
2023-06-27 19:34:25 -07:00
Mark Thompson 76948cffa8 Improve visibility of green icons on white background (#1196) 2023-06-27 10:19:34 +02:00
Mark Thompson 4ea8abb53b Limit title length when too many status icons & shade background of every other icon (#1195)
* Shading alternate icon backgrounds on title bar

* Shading alternate icons on title bar

* Shading alternate icons on title bar

* Clang
2023-06-27 10:15:58 +02:00
gullradriel 32085f317b adding one more character to modulation widget for spec (#1193)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-27 10:02:04 +02:00
Kyle Reed faa4367295 Allow frequency override in Playlist, Folder fixes (#1194)
* Allow frequency to be changed per-entry

* Folder fixes, use TX freq

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-26 19:44:01 +02:00
Mark Thompson 3f8a4957af Moved AK Speaker Disable back to a title bar, and modified Mute Icon to differentiate (#1192)
* Added fn to indicate support for disabling speaker
* Support both mute & speaker disable icons
* Byte grouping for ui_config2_t bitmap
* Fix disabling AK speaker to take effect immediately
2023-06-26 08:08:09 +02:00
gullradriel b5ea81cf96 fix SPEC mode in app (#1191)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-25 16:52:40 -07:00
Kyle Reed e14fa5eab0 New playlist (#1185)
* updated playlist file format
* fix trim and delay part of item
* playlist add/remove tracks
* UI Tweak
2023-06-25 20:40:19 +02:00
Mark Thompson 960ecf616c Update menu icons colors per app state & add contrast to bright icons for improved visibility when Selected (#1189)
* Orange icons vs yellow

* Update app icon colors per current development states

* Darken bright icons on white backgrounds

* Darken bright icons on white backgrounds
2023-06-25 19:48:51 +02:00
gullradriel 996ccb0e40 Recon capture mod (#1190)
* Recon: disabling config while recording
* Capture and Spectre Painter: using freqman set bandwidth option instead of static define
* Recon: light_grey for disable so we can still see the text
2023-06-25 11:33:44 +02:00
Mark Thompson 407fee23b9 Long button press support (#1188) 2023-06-25 11:32:37 +02:00
gullradriel 199570d4a5 flipper inspired freqlists for recon (#1187) 2023-06-25 11:30:09 +02:00
gullradriel 5cd9c377d2 Recon record (#1182)
* added auto record checbox and functions (audio and raw)
* Changed baseband compile option from -O3 to -O2. Trying lower gave unexpeted and crashing results.
* added 650k SPEC bw
* fix hang if wait is -100
* fixing no SPEC support in scanner
2023-06-25 08:16:49 +02:00
Kyle Reed e08273b8b8 Auto layout of top status icons (#1178)
* WIP - updated pmem

* WIP status tray auto layout

* Add settings page for icons

* Add ui_config2 to pmem dump

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-23 16:13:39 -07:00
Kyle Reed 6b0f90c321 Update string_format.cpp (#1184) 2023-06-24 00:08:11 +02:00
Mark Thompson 0dea488e98 Added TouchTunes jukebox icon (#1183)
* Added TouchTunes jukebox icon

* Added TouchTunes jukebox icon

* Added TouchTunes jukebox icon
2023-06-23 15:01:19 -07:00
Mark Thompson e60c4dc34b Paint Roller icon for S.Painter & fix make_bitmap.py to generate Clang-compliant code (#1181)
* Generate Clang-compliant output file

* Paint roller icon for Screen Painter app

* Paint roller icon for Screen Painter app

* Paint roller icon for Spectrum Painter
2023-06-23 07:57:35 -07:00
gullradriel 0c75713820 using emplace_back instead of push_back
* using emplace_back instead of push_back is giving better results in regards of the memory management and crashes
* we tried with all our best ideas, we don't know why it's better but it outperformed every other allocations method tested
2023-06-22 22:26:15 +02:00
gullradriel 6a28987a36 Debug utilities (#1171)
-pmem, receiver and transmitter model dumps
2023-06-22 17:35:21 +02:00
gullradriel 7331979107 Freqman memory fix (#1177)
* Cleanings and setting the limit to a working for all apps one
* fixing a guru memory errors, renaming a leftover variable
* removed unneeded set_dirty
2023-06-22 17:34:20 +02:00
Mark Thompson f22808f8ca Pan & Zoom-in support for GeoMap (#1172)
* Zoom-in support

* Zoom-in support

* Clang formatting

* Clang formatting

* Zoom-in support

* Zoom-in support

* Zoom-in support

* Roll-back wrong file edited

* Zoom-in support

* Clang formatting

* Clang formatting

* Zoom-in support

* Zoom-in support

* Zoom-in support

* Oops copy/paste error
2023-06-21 21:56:07 +02:00
E.T e1c519d71e Frequman widget gets a non owning reference to the database instead of copying (#1175)
-Get rid of -Weffc++ pointer member warnings by restricting copy of FreqManUIList
-Add null checks
-Remove unused macro
2023-06-21 12:05:07 +02:00
Kyle Reed 909f37f89b whipcalc parsing, bug fixes (#1174)
* whipcalc parsing, bug fixes
2023-06-20 20:22:58 +02:00
Kyle Reed 63b0093321 Consolidate metadata file parsing (#1173)
* Add metadata file helper and replace old code
* Finish updating metadata read/write
2023-06-20 09:32:37 +02:00
Mark Thompson fa06df1400 Persistent audio mute support revisions (#1169)
* Don't disable DAC when other audio output is using it
* Persistent audio mute revisions
* Moved persistent audio mute code to audio.cpp
* Make "Disable AK speaker amp" take effect immediately
2023-06-19 21:06:07 +02:00
Kyle Reed f83027d451 Re-add MicTX workaround (#1170) 2023-06-19 21:05:08 +02:00
Kyle Reed 7ebf3d3cdd Fix hang in Audio when switching from Capture app, & bug fixes. (#1167)
* Fix hang in Audio when switching from Capture app
* Bug fixes
2023-06-19 07:48:29 +02:00
Kyle Reed 5daa0dfbb1 Consolidate frequency field on_edit (#1166) 2023-06-18 21:11:04 +02:00
Mark Thompson 7b669d7001 Proposed default focus changes (#1165)
* Changed default focus of pocsag, glass, scanner, sonde
2023-06-18 08:14:29 +02:00
Mark Thompson ba2407d691 Persistent audio mute support (#1161)
* Persistent speaker mute support #1100
* Moving persistent mute support from WM8731/AK4951 codec files to audio.cpp module for simplification
2023-06-17 23:15:21 +02:00
Kyle Reed 5743d3a3b9 Playlist cleanup (#1163)
* Functional rewrite of playlist
2023-06-17 22:01:46 +02:00
Kyle Reed 7f1c0f6f7e Explicitly set tx_model to make MicTX happy (#1162)
Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-17 19:15:46 +02:00
Kyle Reed 650d299c99 Don't update radio directly when saving/restoring tx/rx model settings. (#1160)
* Set rf direction in RadioState

* centralize cpld load for TX->RX

* Remove calls to load_sram from all apps

* re-add CPLD hack, don't set radio values directly.

---------

Co-authored-by: kallanreed <kallanreed@noreply.github.com>
2023-06-17 07:54:52 -07:00
gullradriel 3a5c2da66c fixing bandwidth application, changed UI a bit (#1158)
* fixxing bandwidth application, moved 'continuous' from config page to main page in form of a 'loop' button. Removed looking glass button as it's not configurable to position on a range/signal
2023-06-16 07:50:04 +02:00
Kyle Reed d29826e6f2 Update playlist file parsing, reduce allocs (#1157)
* Update playlist file parsing, reduce allocs
* Cleanup, move impl to cpp
2023-06-15 22:15:26 +02:00
Kyle Reed 34fefd1cad Add file reader (#1155)
* Add file reader
* Add a simple test example of parsing settings.
* Use new FileLineReader to parse Glass presets
* Trim CRLF from Glass preset name
2023-06-15 09:45:13 +02:00
Mark Thompson a5c7eb2fbc Starting freq fixes when no App Settings, and support for App Settings in Scanner App (Mode/BW/Step only) (#1153)
* Fix initial freq when no App Settings tpms
* Default POCSAG freq when no App Settings
* Enable Scanner App Settings for Modulation/BW/Step
* Default SONDE freq when no App Settings
2023-06-14 16:54:19 +02:00
Kyle Reed 6298388fe1 Stash radio settings per app (#1151)
* add a little Stash utility.
* add radio state RAII helper.
* first part of radio_state changes
* add radio_state_ to rest of apps
* fix freq_step and format
* fix unused ui_sigfrx: corrected sample rate, added back freq setting
2023-06-14 09:57:20 +02:00
Kyle Reed 19491ce3f7 Add debug_log, fix tx_bht (#1149) 2023-06-13 08:14:26 +02:00
Kyle Reed 3db2053c21 Allows apps using app settings to use global frequency (#1148)
* Allow apps to not use persisted freq.
* use rx_freq for RX mode
* remove direct call to persistant_memory in tx_model
* app_setting defaults, and tx_view to use ctor value
2023-06-12 19:40:32 +02:00
gullradriel ccd7bd6fc2 Fixing txt_line_processing logic, adding back track number and plalist name (#1146) 2023-06-12 07:25:25 +02:00
Kyle Reed fff78ce00f Faster app_settings save (#1145)
* No-allocation settings save

* remove fixed TODO

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-11 13:35:45 -07:00
Kyle Reed 8bd3d6249d App settings revamp (#1139)
* WIP AppSetting overhaul

* WIP migrating apps to new settings.

* remove settings, rename tuned => target

* formatting

* Minor fixes

* Fix hang on app load

* run formatter

* PR comment fixes

* Load modulation into receiver model in app_settings

* Run formatter

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-11 11:47:13 -07:00
gullradriel f65e743c4c Recon revamp (#1144)
* fixing recon/scanner restarting while on pause and setting a manual start or end
* gui revamping, duplicated options suppressed from config menu
* recon_match_mode set at start
2023-06-11 18:49:28 +02:00
zxkmm 843880d22e playlist: fix track_number out of range and a little fix and alittle ui change (#1142)
* fix playlist track_number out of range
* implement pause
* fix for review: compatibility for old PPL grammar
2023-06-11 18:22:15 +02:00
gullradriel ec3cc7a859 Looking spectrum tune (#1143)
* finely tuned spectrum and marker precision, all modes

* hidding buttons when mode is not supported

* removing on show hide since widget implementation is buggy. Fine tuned values using signal generator to get around the corners

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-10 17:17:02 +02:00
Erwin Ried b1733cbd2a Merge pull request #1141 from u-foka/pmem-debug-rename
improve P.Memory naming consistency
2023-06-10 02:44:31 +02:00
Eisenberger Tamas 4ec2d64cb7 improve P.Memory naming consistency
I swear I wont touch these strings again :D
2023-06-10 00:34:27 +02:00
E.T d370dae170 increase touch sensitivity a little more (#1140)
and make the code a little more descriptive :)

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-09 23:45:26 +02:00
E.T 72f0a1be54 add dockerfile-nogit-arm (#1136)
works on aarc64 docker hosts

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-10 07:52:14 +12:00
E.T a2e5e03f07 Gcc12 related fixes (#1138)
* GCC12 related fixes but still compiles on GCC9

* Suppress warnings about volatile +=

* Enable c++20 if supported by the compiler

On gcc12 we need to use -std=c++20 since constexpr .c_str() on std::string is only officially available since c++20 and the new gcc wouldnt let us use it with older standard

* code format

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-10 07:50:42 +12:00
E.T 47eda54d6a Format script (#1123)
* Add code formatting script

* dont forma the test framework

* use clang-format-13

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-10 07:50:21 +12:00
gullradriel db81b9b1e1 code cleaning, fast and slow mode adjustement, dc pixels displayed (#1137)
* code cleaning, fast and slow mode adjustement, dc pixels displayed

* factorisation, comments

* adjusting offsets for alignement

* indent

---------

Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-09 17:13:44 +02:00
E.T b9de1918b4 fix pmem -> make backup_ram_t data members volatile (#1135)
* fix pmem -> make backup_ram_t data members volatile

plus add calculated crc to the pmem debug screen

* rename pmem debug menu to p.mem

As this is how its refered to in the wiki and other screens

* p.mem looked strange with capital P

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-09 15:52:09 +02:00
dc2dc b87d26f271 Update HAM_70CM_BAND.TXT (#1134)
420-450 max Amateur Radio Band worldwide
2023-06-08 10:05:10 +02:00
jLynx d326ec831c 1.7.2 (#1132)
* Update version.txt

* Update past_version.txt
2023-06-08 17:38:48 +12:00
dc2dc faac5b45c6 Update POCSAG.TXT (#1127)
Added Dapnet frequency for Hams Germany
2023-06-08 06:13:11 +02:00
dc2dc 5e930a7717 Update HAM_2M_BAND.TXT (#1128)
wrong Frequency for Ham Band 2 Meters
2023-06-08 06:12:08 +02:00
dc2dc aff5e22843 Update RADIOSONDE.TXT (#1129)
https://radiosondy.info/
added frequencies and added regions
2023-06-08 06:10:36 +02:00
Mark Thompson 344a8dc0a0 Fix crash when empty FreqMan files (#1113) and Fix missing Comma when editing text (#1125) (#1131)
* Fix crash issues with empty FreqMan files
* Added ability to delete empty files
* Fix missing Comma character when editing text #1125
2023-06-08 05:46:07 +02:00
gullradriel 7576432e44 Fixing file too big,setting VHF as hamradio entry since it's scanning a tx and a rx (#1124)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-08 10:53:25 +12:00
E.T 3ddc753c88 Pmem reset (#1115)
* reset config when holding down select durring boot

it still need to be tripple checked if adc or control init really doesnt use any pmem

* one less magic number :)

* dual key action left+right for pmem reset

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-08 10:52:29 +12:00
E.T d3a7414e51 pmem debug (#1112)
* pmem debug

* remove std::to_string

* fix review findings

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-07 22:32:44 +02:00
gullradriel 1406c81e03 fix for save freq and category position (#1120)
* fix for save freq and category position
* applying conversation fixes, and save keep freqs when changing category
2023-06-07 18:05:12 +02:00
Kyle Reed 0f28fefc82 Consolidate styles (#1116)
* Consolidate styles into a Styles class and clean up code

* Format

* Add style_bg_dark_grey for nav bar

* Fix bugs found in PR

* Rename styles

* Add bg_white style

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
2023-06-07 08:33:32 -07:00
E.T 496b124baf fix missmatched deletes in spectrum painter (#1121) 2023-06-07 15:41:30 +02:00
gullradriel 5a5445e980 Dsp fatfs fix (#1118)
* fixing overflow by one in ../firmware/baseband/dsp_goertzel.cpp by expanding the original array size
* fix underflow by -1 if seq is not stopping the loop
2023-06-07 11:25:35 +02:00
gullradriel eecdd3acda fixing edit freq,edit desc, delete freq (#1117)
* fixing edit freq,edit desc, delete freq
* fixing scrolling issues
* fixing indent
2023-06-07 10:17:55 +02:00
gullradriel c66df8c807 add back correct highlight to list (#1114) 2023-06-07 10:17:29 +02:00
Mark Thompson f2a7617b1b WFM bandwidth widget dial direction (clockwise==increasing) & OptionsField widget change (circular list) (#1109)
* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Put WFM bandwidth options in ascending order

* Clang formatting attempt

* OptionsField wrapping - for your consideration

Perhaps if OptionsWidget supported wrapping, then it would be easier to dial through all available options without having to know which direction to turn the dial.
2023-06-06 18:46:08 +02:00
Kyle Reed af448cf444 Persist audio (#1110)
* Add credits
* Persist audio, add volume to apps
* Remove comment
* Hack around apparent alignment problem
2023-06-05 20:09:50 +02:00
E.T 9c39061590 Touch fix bl timeout (#1108)
* Touch debug from kallanreed

* Revert "init commit for fix touchscreen bad corner (#1071)"

This reverts commit 53fcdedb88.

* increase touch sensitivity by lowering the threshold

---------

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-06-04 22:41:55 +02:00
Kyle Reed 28319652c1 Add AudioVolumeField -- cleanup (#1107)
* WIP Adding AudioVolumeField

* Fix build break

* Add Bernd to about

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-04 12:56:46 -07:00
gullradriel 7e8a139732 moved pmem setting restore before top bar is loaded (#1105)
* moved pmem setting restore before top bar is loaded
* added a check pmem from sdcard function, replaced occurences, added a save to file on back button
* Added defines for flag file and dump file, changed test func name to should_use_sdcard_for_pmem, used file_exists, removed save and load pmem parameter
* changed a text to a more comprehesible one
2023-06-04 21:25:25 +02:00
Mark Thompson e3e179e380 Increase text brightness for aged flights in ADS-B RX (#1106) 2023-06-04 16:56:53 +02:00
Mark Thompson 4f768140c1 Disallow Focus positioning to Empty lists #1097 (#1104)
* Disallow Focus positioning to Empty lists #1097

* Disallow Focus positioning to Empty lists #1097
2023-06-03 23:01:53 -07:00
Kyle Reed e5728b3501 Fileman default open and Screenshot viewer (#1102)
* WIP expensive way

* Add default file handling to fileman

* Remove screenshot_reader and tests

* Read data in chunks

* Format

* Fix error text position

* PR feedback

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-03 19:26:39 -07:00
gullradriel f66f438487 Recon/FreqMan set limit in load_freqman_file_ex (#1098)
* changed a hardcoded value by a define, changed the order for the database clean so it happens first, changed load_freqman_file_ex so it can take a max num of freqs as a parameter with a default to FREQMAN_MAX_PER_FILE
* added a swap and a set_db to clear as much as possible before the loading of a category
* added a define for Recon max per files
* remove unneeded comment
2023-06-03 22:42:34 +02:00
E.T 2fd3bf0136 Sd over usb macos (#1096)
* sd over usb works at least up to intel + big sur, not yet on m1 + ventura
* style
2023-06-02 12:20:11 +02:00
gullradriel c3264f0d15 set tx and rx before launching mic app from recon (#1094)
Co-authored-by: GullCode <gullradriel@hotmail.com>
2023-06-02 17:53:15 +12:00
Kyle Reed 8d7fdeb633 Add edit support for Notepad (#1093)
* WIP file editing

* WIP file editing

* Add "on_pop" handler to navigation.

* WIP Editing

* WIP for draft

* Fix mock and unit tests,  support +newline at end.

* Clean up Painter API and use string_view

* Fix optional rvalue functions

* Fix Result 'take' to be more standard

* FileWrapper stack buffer reads

* Grasping at straws

* Nit

* Move set_on_pop impl to cpp

* Workaround "Open" when file not dirty.

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-06-01 15:45:55 -07:00
gullradriel 69011754c9 simplifications, better precision, less sleeps (#1091)
* simplifications, better precision, less sleeps
* fixing glitches
* fixed res freeze
* correct frequency picking for marker, yay !
* added comments into the code in hard parts
* took out unneeded sleep
Special thanks to users vmakeev, tel, f1ghy, u-foka for their resilience in testing the bunch of bins I've produced to fix the thing :-)
2023-06-01 13:03:32 +02:00
Kyle Reed e50d8dc148 Move file_wrapper and make testable. (#1085)
* WIP Move file_wrapper and make testable.

* More tests, get text_editor compiling

* Back to working

* Run formatter

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-28 08:44:21 -07:00
gullradriel 23c24355ab Yellow coloration for truncated freqman files (#1086) 2023-05-28 17:09:46 +02:00
Brumi-2021 0dcf2af50d Minor GUI change to the Mic App (#1084)
* Mic App GUI tidy up
2023-05-27 18:13:17 +02:00
Bernd Herzog cf07428a5d changed the vendor id of sd to usb (#1082) 2023-05-27 21:39:04 +12:00
E.T 35b381ae77 Fix SD over USB (#1081)
Remove clkin_detect_init() from cpu_clock_init() mirroring upstream change from the hackrf repo

Co-authored-by: Eisenberger Tamas <e.tamas@iwstudio.hu>
2023-05-27 11:06:05 +12:00
gullradriel 0fc748a234 precision marker tweak on single pass (#1079)
* precision marker tweaks
2023-05-26 22:42:12 +02:00
gullradriel 638ea78db9 cosmetic fix on option config (#1080) 2023-05-26 19:47:41 +02:00
gullradriel e81747ef7b fixed wrong function declaration (#1078) 2023-05-26 19:35:17 +02:00
gullradriel 89e24cb358 Freqman mem reduce (#1076)
-made frequency lists a widget to avoid a full frequency_list copy
-reduced FREQMAN_MAX_PER_FILE to a working limit
2023-05-26 18:17:47 +02:00
Kyle Reed 00667cecf9 Notepad menu (#1072)
* WIP notepad menu
2023-05-26 10:02:17 +02:00
zxkmm 53fcdedb88 init commit for fix touchscreen bad corner (#1071)
fix a rounding issue and allow more sensible touchscreen
2023-05-26 09:50:32 +02:00
zxkmm 5bdf9363e9 fix for bad track count (#1069)
* fix for bad track count
* code format
* Thanks kallanreed for providing a more clear logic!
2023-05-25 22:07:27 +02:00
Mark Thompson 6b44a77ef6 Support for Rotary Encoder Dial sensitivity levels, issue #965 (#1057)
* Support for 3 levels of rotary encoder sensitivity #965

Backend support; UI will still need to call set function to configure.

* Support for 3 levels of rotary encoder sensitivity #965

Backend support only.  UI will still need to be changed to call the set_sensitivity() function to configure.

* Removed trailing space

* Deleted blank lines to see if format checker will be happier

* Simpler support for multiple levels of encoder sensitivity, for issue #965

Removed the convoluted code :-) and instead just using a 2-dimensional array to choose which transition map to use.  For now I only have 2 (vs 3) levels enabled as well, to save code space and because high-sensitivity is very touchy.

* Simpler version of configurable encoder sensitivity, issue #965

* Formatting

* Formatting test for Clang

* Formatting test

* Formatting (removed helpful comment)

* Formatting test (remove commented-out code)

* Formatting & swapping medium/low so default mode=0

* Swapped medium/low so default mode=0

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Adding UI & PMEM support to make encoder dial sensitivity configurable, issue #965

* Removed unneeded range check (trusting in pmem checksum)
2023-05-24 19:32:12 -07:00
gullradriel edc6dc819c Recon: reducing memory imprint, live list removal function in Recon (#1058)
* reducing memory imprint before launching a nav
* added remove from loaded list in Recon / delete from output file in Scanner functionality
* fix initial color for filename
2023-05-24 20:52:05 +02:00
Kenny ffd72eb232 ui_spectrum_painter tx gain initialization (#1059)
When starting the app, actual tx gain is 47 while the ui shows 10. "Increasing" gain to 11 takes effect, reducing actual gain as displayed in UI. This change makes the UI gain value consistent with actual tx gain.
2023-05-24 19:54:40 +02:00
Kyle Reed feee1f7ab3 Set default TX gain to 35 (#1062)
* Set default TX gain to 10

I'd rather people who know better have to turn this up, than people who don't know better to be jamming their neighbors accidentally.

* Update transmitter_model.hpp

Set default to 35 per discussion.
2023-05-24 19:31:59 +02:00
Kyle Reed 2d79cd4b24 Fix button_exit width (#1064)
I moved the exit button without making it narrower in the last change. This shrinks the width, so it fits on the screen.
2023-05-24 19:20:48 +02:00
Kyle Reed 637bcfdbc7 Use receiver/transmitter models everywhere (#1056)
* Use receiver/transmitter models everywhere

* Run formatter

* Fix a copy-paste bug, make transmitter_model actually set tx_gain.

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-23 21:22:30 -07:00
zxkmm 22c9dd31bf playlist fix -fix for review comments (#1054) 2023-05-23 18:28:50 +02:00
Brumi-2021 9e63d80667 Solving_error_dfu_util_v_011_integrated_vscode (#1055) 2023-05-23 18:28:34 +02:00
Mark Thompson 67b5b57533 Support "repeat" when a direction button is held down (#1053)
* Support "repeat" when holding a direction button

* Support "repeat" when a direction button is held

* Support "repeat" when a direction button is held

* Support "repeat" when a direction button is held

* Formatting violation - removed a trailing space

* Removed unneeded return() statement
2023-05-23 18:55:26 +12:00
Kyle Reed c2314f4838 Support open in notepad from fileman (#1052)
* Support open in notepad from fileman
* Align Filename Exit button with UI
2023-05-23 06:40:03 +02:00
zxkmm 1cb682473a playlist fix (#1043)
* first commit for playlist fix
* format for playlist fix
* playlist fix -textual change
thanks @kallanreed for giving the new name of the extension XD
2023-05-23 06:39:22 +02:00
Mark Thompson 63dd85cbfc Fixed logic error in unused code module (#1049) 2023-05-23 16:26:16 +12:00
gullradriel 6dbc8460b4 hackrf submodule update (#1048) 2023-05-22 23:28:47 +02:00
gullradriel ec8bf04baa looking glass: correct max_hold computation with new modes (#1047) 2023-05-22 23:24:46 +02:00
Mark Thompson a1189faad1 Fixed Scanner RESUME button when delay timer Wsa==0, issue #1041 (#1046) 2023-05-22 23:21:00 +02:00
gullradriel ba9cc55b5a add original or complementary author (#1045) 2023-05-22 22:17:28 +02:00
gullradriel c2e527bce2 removed unneeded RECON directory (#1044) 2023-05-22 22:16:29 +02:00
Kyle Reed 98f3bf151f Lazy line caching for Notepad (#1042)
* easier 'now', start adding text editor

* Adding scrolling to notepad

* Better scrolling

* Better scrolling, off-by-1 bugs

* MVP fit and finish

* Add tiny font and use in Notepad

* Font tweaking, tiny font cursor

* Fix warning

* Format changed files

* WIP No file limit

* WIP - adding CircularBuffer type

* WIP Caching

* add  unit test for circular_buffer

* WIP still have a bug when moving cache forward

* Finish lazy line caching

---------

Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-22 13:08:59 -07:00
Erwin Ried 802a4e243b Update README.md (#1040) 2023-05-22 20:07:44 +02:00
jLynx 215ad4fd4c 1.7.1 (#1037)
* Update past_version.txt

* Update version.txt
2023-05-22 21:21:47 +12:00
Erwin Ried 7150d95a98 cleanup (#1036) 2023-05-22 21:14:35 +12:00
gullradriel 80636c53da fix CONFIG not working if nothing changed and got less flickering by separating text output (#1034)
-fix CONFIG dysfunction if nothing had changed: this was a reminiscence of the nothread conversion
-addressed screen flickering: reduced it by separating two text output
-variables: took out unneeded userpause (reminiscence of the nothread conversion)
-solved problem with manual range not updating description on consecutive clicks
-solved problem with direction changing on 'UNLOCK/SKIPLCK'
2023-05-22 10:49:07 +02:00
jLynx 874d3c251c Update dfu_hackrf_one.bat (#1032) 2023-05-22 13:03:53 +12:00
Kyle Reed 6c541af0fd Move tests, add applicaiton tests. (#1031)
Co-authored-by: kallanreed <kallanreed@outlook.com>
2023-05-21 17:33:12 -07:00
Mark Thompson 4d1269051b Fixed debounce code to handle noisy switch transitions #989 (#1030)
* Fix debounce to handle noisy switch transitions

* Fix debounce to handle noisy switch transitions

* Fix debounce to handle noisy switch transitions

* Removed inline comment to see if Clang checker will be happy

* Test fix for supposed Clang formatting issue
2023-05-22 12:06:37 +12:00
gullradriel be1b2716d6 freq correct unit fix (#1029)
* fix units for frequency offset. It was planned to be a uint32_t since a uint16_t is obviously too short
* took the uint32_t limitation for frequency offset in account
2023-05-21 23:47:02 +02:00
Bernd Herzog 34d46a9d5d added first unit test (#1027)
* added first unit test

* improved unit test names
2023-05-21 23:08:24 +02:00
gullradriel cac3a7cf1e Settings directory fix in Level and Recon (#1028)
* removed unneeded sdcard check and directory creation
* changed settings so they use SETTINGS and a better name. Code beautication
2023-05-21 22:31:03 +02:00
gullradriel 0e86e63e97 Fixing colorisation of wait/lockwait fields (#1026)
* Fixing colorisation of wait/lockwait field
2023-05-21 20:42:54 +02:00
gullradriel c0ff5715de fixing bad range for lock field (#1024)
* fixing bad range for lock field
2023-05-21 09:11:58 +02:00
gullradriel 24ce1b9893 fixed bad copy paste from #1016, thanks @notherngineer (#1023)
fixed bad copy paste from #1016
2023-05-21 08:59:42 +02:00
jLynx edd8e7c3b9 Automatically add world map to SD card files (#1022)
* Testing

* Updated scripts

* Updated removed testing file
2023-05-21 18:42:38 +12:00
649 changed files with 67959 additions and 12157 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
custom: # https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
+1 -1
View File
@@ -9,7 +9,7 @@ assignees: ''
----
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/eried/portapack-mayhem/releases)
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases)
----
@@ -9,8 +9,8 @@ assignees: ''
----
Before creating this issue, **do the following**:
* Read the Wiki on booting: https://github.com/eried/portapack-mayhem/wiki/Won't-boot
* Read: https://github.com/eried/portapack-havoc/wiki/Update-firmware
* Read the Wiki on booting: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
* Read: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
* Watch carefully: https://www.youtube.com/watch?v=_zx4ZvurgOs
* (if you are not in Windows) also check: https://www.youtube.com/watch?v=kjFB58Y1TAo
@@ -27,12 +27,12 @@ Steps to reproduce the behavior:
**My Hardware**
Please specify what PortaPack hardware version you are using.
You can find the list of versions here: https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions
You can find the list of versions here: https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions
**Affected versions**
Please tell us what version you are running.
If your device is still functional, try the latest nightly release before submitting this.
You can find the latest nightly version here https://github.com/eried/portapack-mayhem/releases
You can find the latest nightly version here https://github.com/portapack-mayhem/mayhem-firmware/releases
**Were you able to update the firmware before?**
Things might be confusing the first time, please check the video available on the link above.
+44 -17
View File
@@ -1,28 +1,55 @@
import os
import re
import sys
raw_git = os.popen('git log next --since="24 hours" --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
import requests
from datetime import datetime, timedelta, timezone
# Set up your personal access token and the repository details
token = os.environ.get('GH_TOKEN')
repo_owner = "portapack-mayhem"
repo_name = "mayhem-firmware"
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
def print_stable_changelog(previous_sha):
url = f"compare/{previous_sha}...next"
commits = handle_get_request(url)
for commit in commits["commits"]:
# Print the commit details
print(format_output(commit))
pattern = re.compile("[$@+&?].*[$@+&?]")
if pattern.match(stripped):
stripped = re.sub("[$@+&?].*[$@+&?]", "", stripped)
stripped = re.match(r'.+?(?=-)', stripped).group(0)
def print_nightly_changelog():
# Calculate the date and time 24 hours ago
since_time = (datetime.now(timezone.utc) - timedelta(hours=24)).isoformat() # Check that this is UTC
url = "commits"
commits = handle_get_request(url, since_time)
for commit in commits:
# Print the commit details
print(format_output(commit))
def handle_get_request(path, offset=None):
headers = {} if token == None else {"Authorization": f"Bearer {token}"}
params = {"since": offset}
url_base = f"https://api.github.com/repos/{repo_owner}/{repo_name}/"
response = requests.get(url_base + path, headers=headers, params=params)
# Check if the request was successful (status code 200)
if response.status_code == 200:
return response.json()
else:
stripped = re.sub(r'^.*?-', "", stripped)
return "@" + stripped
print(f"Request failed with status code: {response.status_code}")
return None
def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
return line
def format_output(commit):
message_lines = commit["commit"]["message"].split("\n")
author = commit["author"]["login"] if commit["author"] and "login" in commit["author"] else commit["commit"]["author"]["name"]
return '- ' + commit["sha"][:8] + ' - @' + author + ': ' + message_lines[0]
for row in raw_git.splitlines():
print(compile_line(row))
if len(sys.argv) < 2:
print_nightly_changelog()
else:
past_version = sys.argv[1]
print_stable_changelog(past_version)
+42 -6
View File
@@ -20,7 +20,7 @@ jobs:
- name: check latest commit is less than a day
id: should_run
continue-on-error: true
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
build:
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
@@ -28,15 +28,20 @@ jobs:
steps:
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Get version date
id: version_date
run: echo "::set-output name=date::n_$(date +'%y%m%d')"
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
ref: next
#ref: next
# The branch, tag or SHA to checkout. When checking out the repository that
# triggered a workflow, this defaults to the reference or SHA for that event.
# Otherwise, uses the default branch.
# https://github.com/actions/checkout
# So scheduled runs will use the default branch (next) but its now possible to trigger a workflow from another branch
submodules: true
- name: Git Sumbodule Update
run: |
@@ -47,19 +52,30 @@ jobs:
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
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 && 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: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py)
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
echo "::set-output name=content::$CHANGELOG"
echo "content=$CHANGELOG" >> $GITHUB_OUTPUT
id: changelog
- name: Create Release
id: create_release
@@ -77,6 +93,16 @@ jobs:
${{ steps.changelog.outputs.content }}
draft: false
prerelease: true
- name: Upload Firmware TAR Asset
id: upload-firmware-tar-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar
asset_content_type: application/x-tar
- name: Upload Firmware Asset
id: upload-firmware-asset
uses: actions/upload-release-asset@v1
@@ -97,3 +123,13 @@ jobs:
asset_path: ./sdcard.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
+48 -11
View File
@@ -9,41 +9,57 @@ jobs:
steps:
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
ref: next
#ref: next
# The branch, tag or SHA to checkout. When checking out the repository that
# triggered a workflow, this defaults to the reference or SHA for that event.
# Otherwise, uses the default branch.
# https://github.com/actions/checkout
# So scheduled runs will use the default branch (next) but its now possible to trigger a workflow from another branch
submodules: true
- name: Git Sumbodule Update
run: |
git submodule update --init --recursive
- name: Get version
id: version
run: echo "::set-output name=version::$(cat .github/workflows/version.txt)"
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
- name: Get past version
id: past_version
run: echo "::set-output name=past_version::$(cat .github/workflows/past_version.txt)"
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
- name: Build the Docker image
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
- name: Make build folder
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
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 && 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: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/stable_changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
echo "::set-output name=content::$CHANGELOG"
echo "content=$CHANGELOG" >> $GITHUB_OUTPUT
id: changelog
- name: Create Release
id: create_release
@@ -55,21 +71,32 @@ jobs:
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
body: |
**Stable release - ${{ steps.version.outputs.version }}**
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/). Please check the [readme](https://github.com/eried/portapack-mayhem/blob/master/README.md) for details.
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/). Please check the [readme](https://github.com/portapack-mayhem/mayhem-firmware/blob/master/README.md) for details.
## Release notes
### Revision (${{ steps.version.outputs.version }}):
${{ steps.changelog.outputs.content }}
**Full Changelog**: https://github.com/eried/portapack-mayhem/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
**Full Changelog**: https://github.com/portapack-mayhem/mayhem-firmware/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
## Installation
Check the [wiki](https://github.com/eried/portapack-havoc/wiki/Update-firmware) for details how to upgrade.
Check the [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware) for details how to upgrade.
__Warning:__ Since release 1.8.0, some applications has been moved to the SD card as we ran out of flash space.
### MicroSD card files
For certain functionality, like the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
For certain functionality, like external apps, the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
draft: true
prerelease: false
- name: Upload Firmware TAR Asset
id: upload-firmware-tar-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
asset_name: mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar
asset_content_type: application/x-tar
- name: Upload Firmware Asset
id: upload-firmware-asset
uses: actions/upload-release-asset@v1
@@ -90,4 +117,14 @@ jobs:
asset_path: ./sdcard.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
+1 -1
View File
@@ -1 +1 @@
v1.6.0
v1.9.0
-30
View File
@@ -1,30 +0,0 @@
import os
import re
import sys
past_version = sys.argv[1]
raw_git = os.popen('git log ' + past_version + '..next --pretty=format:"- %h - {USERNAME}*+%al-%an*: %s"').read()
def compute_username(line):
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0)
pattern = re.compile("[$@+&?].*[$@+&?]")
if pattern.match(stripped):
stripped = re.sub("[$@+&?].*[$@+&?]", "", stripped)
stripped = re.match(r'.+?(?=-)', stripped).group(0)
else:
stripped = re.sub(r'^.*?-', "", stripped)
return "@" + stripped
def compile_line(line):
username = compute_username(line)
line = re.sub(r'[*].*[*]', "", line)
line = line.replace("{USERNAME}", username)
return line
for row in raw_git.splitlines():
print(compile_line(row))
+1 -1
View File
@@ -1 +1 @@
v1.7.0
v1.9.1
+3 -2
View File
@@ -49,16 +49,17 @@
*.map
*.lst
.dep/
build/
/build*
CMakeFiles/
# Debugging
.gdbinit*
# Editor files
# Editor/IDE files
*.sublime-project
*.sublime-workspace
.vscode
.idea
# VSCodium extensions
.history
-55
View File
@@ -1,55 +0,0 @@
language: cpp
matrix:
include:
- os: linux
compiler: gcc
cache: apt
dist: xenial
env:
global:
- PROJECT_NAME=PortaPack-HAVOC
- SHORT_COMMIT_HASH=`git rev-parse --short HEAD`
- VERSION_STRING=nightly-$SHORT_COMMIT_HASH
- BUILD_DATE="`date +%Y-%m-%d`"
- BUILD_NAME="$PROJECT_NAME-$BUILD_DATE-$SHORT_COMMIT_HASH"
- ARTEFACT_BASE=$TRAVIS_BUILD_DIR/artefacts/
- ARTEFACT_PATH=$ARTEFACT_BASE/$BUILD_NAME
before_install:
- sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa -y
- sudo apt-get update -q
- sudo apt-get install gcc-arm-embedded -y
script:
# TODO: Introduce top-level Makefile, this is lame.
- sed -e "s/\#set(VERSION.*/set(VERSION \"$VERSION_STRING\")/" -i".bak" CMakeLists.txt
- mkdir build/
- pushd build/
- cmake ..
- make firmware
- popd
after_success:
- mkdir -p $ARTEFACT_PATH
# Copy firmware to firmware-bin directory
- cd $TRAVIS_BUILD_DIR/build
- cp firmware/portapack-h1-havoc.bin $ARTEFACT_PATH/
- cp hackrf/firmware/hackrf_usb/hackrf_usb.dfu $ARTEFACT_PATH/
- cd $TRAVIS_BUILD_DIR
- cp LICENSE $ARTEFACT_PATH/
# Build the archive
- cd $ARTEFACT_BASE
- tar -cJvf $ARTEFACT_BASE/$BUILD_NAME.tar.xz $BUILD_NAME
- md5sum --binary $BUILD_NAME.tar.xz >MD5SUMS
- sha256sum --binary $BUILD_NAME.tar.xz >SHA256SUMS
addons:
apt:
packages:
- coreutils
- tar
- sed
- cmake
- dfu-util
+17 -2
View File
@@ -18,15 +18,17 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 2.8.9)
cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
project(portapack-h1)
set(EXPECTED_GCC_VERSION "9.2.1")
set(VERSION "$ENV{VERSION_STRING}")
if ("$ENV{VERSION_STRING}" STREQUAL "")
if ("${VERSION}" STREQUAL "")
execute_process(
COMMAND git log -n 1 --format=%h
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
@@ -40,8 +42,20 @@ if ("$ENV{VERSION_STRING}" STREQUAL "")
else (GIT_VERSION_FOUND)
set(VERSION "${GIT_VERSION}")
endif (GIT_VERSION_FOUND)
set(VERSION_NOHASH "dev")
else()
set(VERSION_NOHASH "${VERSION}")
endif()
execute_process(
COMMAND bash "-c" "echo ${VERSION} | md5sum | head -c 8"
OUTPUT_VARIABLE VERSION_MD5
)
set(VERSION_MD5 "0x${VERSION_MD5}")
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
@@ -58,4 +72,5 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
enable_testing()
add_subdirectory(firmware)
+6 -6
View File
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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
@@ -329,11 +329,11 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Public License instead of this License.
-7
View File
@@ -1,7 +0,0 @@
# License
Portapack Mayhem is distributed under [GPL v3.0](LICENSE), however some sub projects might have different (GPL v3.0 compatible) licenses.
## Other Licenses (compatible with GPL v3.0)
- Most of Portapack Mayhem is distributed under GPL-2.0-or-later. [license](LICENSE.GPL-2.0-or-later)
- ChibiOS is distributed under GPL v3.0. [license](/firmware/chibios/license.txt)
- Portapack-ChibiOS is distributed under Apache-2.0 [license](http://www.apache.org/licenses/LICENSE-2.0)
+26 -21
View File
@@ -1,26 +1,33 @@
> [!IMPORTANT]
> this repository **has just been moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware). Please keep this in mind to **update your links** accordingly.
# PortaPack Mayhem
[![Build Status](https://travis-ci.com/eried/portapack-mayhem.svg?branch=master)](https://travis-ci.com/eried/portapack-mayhem) [![Nightly Release](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/eried/portapack-mayhem/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/eried/portapack-mayhem/total)](https://github.com/eried/portapack-mayhem/releases) [![GitHub Releases](https://img.shields.io/github/downloads/eried/portapack-mayhem/latest/total)](https://github.com/eried/portapack-mayhem/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.svg)](https://discord.gg/tuwVMv3) [![Check bounties!](https://img.shields.io/bountysource/team/portapack-mayhem/activity?color=%2333ccff&label=bountysource%20%28USD%29&style=plastic)](https://www.bountysource.com/teams/portapack-mayhem/issues)
[![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)
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/eried/portapack-mayhem/img/hw_overview_h2_front.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/eried/portapack-mayhem/img/hw_overview_h2_inside.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview#portapack-internals)
[<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://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure)*
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
# Quick overview
# What is this?
If you are new to *HackRF+PortaPack+Mayhem*, there is an awesome introductory video by [Tech Minds](https://www.youtube.com/channel/UC9a8Z6Sp6eb2s3O79pX5Zvg) available:
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
[![Setup and overview](https://img.youtube.com/vi/kjFB58Y1TAo/0.jpg)](https://www.youtube.com/watch?v=kjFB58Y1TAo)
[![Beginner's Guide To The HackRF & Portapak With Mayhem](https://img.youtube.com/vi/H-bqdWfbhpg/0.jpg)](https://www.youtube.com/watch?v=H-bqdWfbhpg)
For people familiar with the [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware), this one might be interesting:<br>[What is the HackRF One Portapack H2+?](https://www.youtube.com/watch?v=alrFbY5vxt4)
# Frequently Asked Questions
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) is always welcomed and appreciated.
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) is always welcomed and appreciated.
## What to buy?
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX) pack, that includes everything you need. Sadly, the people making the H2 never made the updated schematics available (against the terms of the license).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), 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: Our friends at OpenSourceSDRLab give away five units every three months in our discord (check the badge on top) of their [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too by ordering.
:heavy_check_mark: Another popular option is the clone of the [PortaPack H1](https://s.click.aliexpress.com/e/_Dkbqs2X).
@@ -28,35 +35,33 @@ This repository expands upon the previous work by many people and aims to consta
![image](https://user-images.githubusercontent.com/1091420/214579017-9ad970b9-0917-48f6-a550-588226d3f89b.png)
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
![image](https://user-images.githubusercontent.com/1091420/214581333-424900ee-26f8-4e96-be2f-69d8dc995ba9.png)
## Where is the latest version?
The current **stable release** is on the [![GitHub release (latest by date)](https://img.shields.io/github/v/release/eried/portapack-mayhem?label=Releases&style=social)](https://github.com/eried/portapack-mayhem/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/eried/portapack-mayhem/releases/).
The current **stable release** is on the [![GitHub release (latest by date)](https://img.shields.io/github/v/release/portapack-mayhem/mayhem-firmware?label=Releases&style=social)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/portapack-mayhem/mayhem-firmware/releases/).
## How can I collaborate
You can write [documentation](https://github.com/eried/portapack-mayhem/wiki), fix bugs and [answer issues](https://github.com/eried/portapack-mayhem/issues) or add new functionality. Please check the following [guide](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) with details.
You can write [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki), fix bugs and [answer issues](https://github.com/portapack-mayhem/mayhem-firmware/issues) or add new functionality. Please check the following [guide](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) with details.
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/eried/portapack-mayhem/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
[![Contributors](https://contrib.rocks/image?repo=eried/portapack-mayhem)](https://github.com/eried/portapack-mayhem/graphs/contributors)
[![Contributors](https://contrib.rocks/image?repo=portapack-mayhem/mayhem-firmware)](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors)
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).
As a last option, if you want to send money directly to me for getting more boards, antennas and such:
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CBPQA4HRRPJQ6&source=url)
## What if I really want something specific?
If what you need can be relevant in general, you can [request a feature](https://github.com/eried/portapack-mayhem/issues/new?labels=enhancement&template=feature_request.md).
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).
You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/eried/portapack-mayhem/issues/new/choose) or [choosing](https://github.com/eried/portapack-mayhem/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).
<del>You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose) or [choosing](https://github.com/portapack-mayhem/mayhem-firmware/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).</del>
Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).
<del>Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).</del>
Bountysource has not been reliable lately, so until this changes, please **DO NOT** post a bounty there. 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/eried/portapack-mayhem/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/eried/portapack-mayhem/issues/new/choose).
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).
You can reach the [official community](https://www.facebook.com/groups/177623356165819) in Facebook, and our Discord by clicking the chat badge on [top](#portapack-mayhem).
+22 -14
View File
@@ -10,32 +10,40 @@ VOLUME /havoc
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 && \
apt-get -qy autoremove
RUN apt-get update \
&& apt-get install -y git tar wget dfu-util cmake python3 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
RUN ln -s /usr/bin/python3 /usr/bin/python && \
ln -s /usr/bin/pip3 /usr/bin/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
# Grab the GNU ARM toolchain from arm.com
# Then extract contents to /opt/build/armbin/
RUN mkdir /opt/build && cd /opt/build && \
wget -O gcc-arm-none-eabi $ARMBINURL && \
mkdir armbin && \
tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
RUN mkdir /opt/build \
&& cd /opt/build \
&& wget -O gcc-arm-none-eabi $ARMBINURL \
&& mkdir armbin \
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
# Configure CCACHE
RUN mkdir ~/bin && cd ~/bin && \
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
RUN mkdir ~/bin \
&& cd ~/bin \
&& for tool in gcc g++ cpp c++; do \
ln -s $(which ccache) arm-none-eabi-$tool \
; done
CMD cd .. && cd build && \
cmake .. && make firmware
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# replace make with ninja temporarily while building your image if you prefer to use that by default
CMD ["make"]
+1 -1
View File
@@ -24,4 +24,4 @@ RUN mkdir ~/bin && cd ~/bin && \
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
CMD cd .. && cd build && \
cmake .. && make firmware
cmake .. && make ppfw
+49
View File
@@ -0,0 +1,49 @@
FROM ubuntu:xenial
# 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.
ENV ARMBINURL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-aarch64-linux.tar.bz2?revision=4583ce78-e7e7-459a-ad9f-bff8e94839f1&rev=4583ce78e7e7459aad9fbff8e94839f1&hash=C9B942D74CEA05FF9DE28380F267BB7D3F3B17A6 \
PATH=$HOME/bin:$PATH:/opt/build/armbin/bin
#Create volume /havoc/bin for compiled firmware binaries
VOLUME /havoc
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 -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
# Grab the GNU ARM toolchain from arm.com
# Then extract contents to /opt/build/armbin/
RUN mkdir /opt/build \
&& cd /opt/build \
&& wget -O gcc-arm-none-eabi $ARMBINURL \
&& mkdir armbin \
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
# Configure CCACHE
RUN mkdir ~/bin \
&& cd ~/bin \
&& for tool in gcc g++ cpp c++; do \
ln -s $(which ccache) arm-none-eabi-$tool \
; done
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# replace make with ninja temporarily while building your image if you prefer to use that by default
CMD ["make"]
+2 -2
View File
@@ -1,6 +1,6 @@
<head>
<meta http-equiv="refresh" content="0; URL=https://github.com/eried/portapack-mayhem/" />
<meta http-equiv="refresh" content="0; URL=https://github.com/portapack-mayhem/mayhem-firmware/" />
</head>
<body>
<p>If you are not redirected, <a href="https://github.com/eried/portapack-mayhem/">click here</a>.</p>
<p>If you are not redirected, <a href="https://github.com/portapack-mayhem/mayhem-firmware/">click here</a>.</p>
</body>
+63 -4
View File
@@ -18,6 +18,8 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.5)
project(firmware)
set(BASEBAND ${PROJECT_SOURCE_DIR}/baseband)
@@ -26,15 +28,29 @@ set(CHIBIOS ${PROJECT_SOURCE_DIR}/chibios)
set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack)
set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py)
set(EXPORT_EXTERNAL_APP_IMAGES ${PROJECT_SOURCE_DIR}/tools/export_external_apps.py)
set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py)
set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py)
set(LZ4 lz4)
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
# In our current build container cmake need a little help to get the version :)
if(NOT DEFINED ${CMAKE_CXX_COMPILER_VERSION})
execute_process(COMMAND bash "-c" "arm-none-eabi-g++ -v 2>&1 | grep 'gcc version' | awk '{print $3}'" OUTPUT_VARIABLE CMAKE_CXX_COMPILER_VERSION)
string(STRIP ${CMAKE_CXX_COMPILER_VERSION} CMAKE_CXX_COMPILER_VERSION)
endif()
set(GCC_VERSION_MISMATCH 0)
if(NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_EQUAL ${EXPECTED_GCC_VERSION})
set(GCC_VERSION_MISMATCH 1)
endif()
add_subdirectory(application)
add_subdirectory(baseband)
add_subdirectory(test)
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
add_custom_command(
@@ -46,16 +62,48 @@ add_custom_command(
)
add_custom_target(
firmware ALL
firmware
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
)
if(${GCC_VERSION_MISMATCH})
set(COMPILER_MISMATCH_MESSAGE "WARNING: Compiler version mismatch, please use the official compiler version ${EXPECTED_GCC_VERSION} when sharing builds! Current compiler version: ${CMAKE_CXX_COMPILER_VERSION}")
message(${COMPILER_MISMATCH_MESSAGE})
add_custom_command(
TARGET firmware POST_BUILD
COMMAND echo ${COMPILER_MISMATCH_MESSAGE}
VERBATIM)
endif()
add_custom_target(
program
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE}
COMMAND ${PROJECT_SOURCE_DIR}/tools/enter_mode.sh hackrf
COMMAND dfu-util --device 1fc9:000c --download ${HACKRF_FIRMWARE_DFU_IMAGE} || (exit 0) # We need to add it for dfu-utils v.011 , (in v.09 it is not necessary)
COMMAND sleep 3s
COMMAND hackrf_spiflash -w ${FIRMWARE_FILENAME}
DEPENDS ${FIRMWARE_FILENAME}
COMMAND hackrf_spiflash -i -R -w ${FIRMWARE_FILENAME}
DEPENDS firmware ${FIRMWARE_FILENAME}
)
add_custom_target(
program-external-apps
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${PROJECT_SOURCE_DIR}/tools/copy_external_apps.sh
DEPENDS program
)
add_custom_command(
OUTPUT ${PPFW_FILENAME}
COMMAND rm -rf firmware_tar
COMMAND mkdir -p firmware_tar/FIRMWARE
# Using VERSION_NOHASH to avoid dev builds piling up in the FIRMWARE folder of the sd card of testers in #test-drive
COMMAND cp ${FIRMWARE_FILENAME} firmware_tar/FIRMWARE/portapack-mayhem_${VERSION_NOHASH}.bin
COMMAND mkdir -p firmware_tar/APPS
COMMAND cp application/*.ppma firmware_tar/APPS
COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
DEPENDS firmware ${FIRMWARE_FILENAME}
# Dont use VERBATIM here as it prevents usage of globbing (*)
# There shouldnt be any funny business in the filenames above :)
)
# TODO: Bad hack to fix location of LICENSE file for tar.
@@ -77,7 +125,18 @@ add_custom_command(
DEPENDS ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
)
add_custom_target(
ppfw ALL
DEPENDS ${PPFW_FILENAME}
)
add_custom_target(
oci
DEPENDS ${PPFW_FILENAME}
)
add_custom_target(
release
DEPENDS MD5SUMS SHA256SUMS
)
+87 -37
View File
@@ -27,16 +27,24 @@
enable_language(C CXX ASM)
include(CheckCXXCompilerFlag)
project(application)
# Compiler options here.
set(USE_OPT "-Os -g --specs=nano.specs")
set(USE_OPT "-Os -g --specs=nano.specs -specs=nosys.specs")
# C specific options here (added to USE_OPT).
set(USE_COPT "-std=gnu99")
# C++ specific options here (added to USE_OPT).
set(USE_CPPOPT "-std=c++17 -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized")
check_cxx_compiler_flag("-std=c++20" cpp20_supported)
if(cpp20_supported)
set(USE_CPPOPT "-std=c++20")
else()
set(USE_CPPOPT "-std=c++17")
endif()
set(USE_CPPOPT "${USE_CPPOPT} -flto -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
# Enable this if you want the linker to remove unused code and data
set(USE_LINK_GC yes)
@@ -89,6 +97,7 @@ include(${CHIBIOS_PORTAPACK}/os/ports/GCC/ARMCMx/LPC43xx_M0/port.cmake)
include(${CHIBIOS}/os/kernel/kernel.cmake)
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
include(${CHIBIOS}/test/test.cmake)
include(external/external.cmake)
# Define linker script file here
set(LDSCRIPT ${PORTLD}/LPC43xx_M0.ld)
@@ -104,12 +113,23 @@ set(CSRC
${BOARDSRC}
${FATFSSRC}
firmware_info.c
usb_serial_cdc.c
usb_serial_descriptor.c
usb_serial_endpoints.c
usb_serial_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
${HACKRF_PATH}/firmware/common/usb_request.c
${HACKRF_PATH}/firmware/common/usb_standard_request.c
${CHIBIOS}/os/various/chprintf.c
)
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
# setting.
set(CPPSRC
main.cpp
shell.cpp
${COMMON}/acars_packet.cpp
${COMMON}/adsb.cpp
${COMMON}/adsb_frame.cpp
@@ -153,6 +173,7 @@ set(CPPSRC
${COMMON}/ui_painter.cpp
${COMMON}/ui_text.cpp
${COMMON}/ui_widget.cpp
${COMMON}/ui_language.cpp
${COMMON}/utility.cpp
${COMMON}/wm8731.cpp
${COMMON}/performance_counter.cpp
@@ -167,15 +188,26 @@ set(CPPSRC
#emu_cc1101.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
file.cpp
freqman_db.cpp
freqman.cpp
io_convert.cpp
io_file.cpp
io_wave.cpp
iq_trim.cpp
irq_controls.cpp
irq_lcd_frame.cpp
irq_rtc.cpp
log_file.cpp
metadata_file.cpp
portapack.cpp
usb_serial_shell.cpp
usb_serial_shell_filesystem.cpp
usb_serial_event.cpp
usb_serial_thread.cpp
usb_serial.cpp
usb_serial_host_to_device.cpp
qrcodegen.cpp
radio.cpp
receiver_model.cpp
@@ -218,80 +250,89 @@ set(CPPSRC
ui/ui_btngrid.cpp
ui/ui_receiver.cpp
ui/ui_rssi.cpp
ui/ui_freqlist.cpp
ui/ui_tv.cpp
ui/ui_spectrum.cpp
ui/ui_styles.cpp
ui/ui_tabview.cpp
ui/ui_textentry.cpp
ui/ui_tone_key.cpp
ui/ui_transmitter.cpp
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
# apps/analog_tv_app.cpp
apps/ble_comm_app.cpp
apps/ble_rx_app.cpp
apps/ble_tx_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
# apps/gps_sim_app.cpp
# apps/lge_app.cpp
apps/pocsag_app.cpp
# apps/replay_app.cpp
apps/soundboard_app.cpp
apps/tpms_app.cpp
apps/tpms_app.cpp
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
apps/ui_adsb_tx.cpp
apps/ui_afsk_rx.cpp
apps/ui_aprs_rx.cpp
apps/ui_btle_rx.cpp
apps/ui_nrf_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_bht_tx.cpp
apps/ui_dfu_menu.cpp
apps/ui_coasterp.cpp
apps/ui_btle_rx.cpp
# apps/ui_coasterp.cpp
apps/ui_debug.cpp
apps/ui_dfu_menu.cpp
apps/ui_encoders.cpp
apps/ui_fileman.cpp
apps/ui_flash_utility.cpp
apps/ui_sd_over_usb.cpp
apps/ui_freqman.cpp
apps/ui_jammer.cpp
#apps/ui_keyfob.cpp
apps/ui_lcr.cpp
apps/lge_app.cpp
apps/ui_fsk_rx.cpp
apps/ui_iq_trim.cpp
# apps/ui_jammer.cpp
# apps/ui_keyfob.cpp
# apps/ui_lcr.cpp
apps/ui_level.cpp
apps/ui_looking_glass_app.cpp
apps/ui_mictx.cpp
apps/ui_modemsetup.cpp
apps/ui_morse.cpp
# apps/ui_nrf_rx.cpp
# apps/ui_nuoptix.cpp
apps/ui_playlist.cpp
apps/ui_pocsag_tx.cpp
apps/ui_rds.cpp
apps/ui_recon_settings.cpp
apps/ui_recon.cpp
apps/ui_remote.cpp
apps/ui_scanner.cpp
apps/ui_search.cpp
apps/ui_sd_over_usb.cpp
apps/ui_sd_wipe.cpp
apps/ui_search.cpp
apps/ui_settings.cpp
apps/ui_siggen.cpp
apps/ui_sonde.cpp
# apps/ui_spectrum_painter_image.cpp
# apps/ui_spectrum_painter_text.cpp
# apps/ui_spectrum_painter.cpp
apps/ui_ss_viewer.cpp
apps/ui_sstvtx.cpp
apps/ui_subghzd.cpp
# apps/ui_test.cpp
apps/ui_text_editor.cpp
apps/ui_tone_search.cpp
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_weatherstation.cpp
apps/ui_whipcalc.cpp
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/analog_tv_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/lge_app.cpp
apps/pocsag_app.cpp
apps/replay_app.cpp
apps/ui_playlist.cpp
apps/gps_sim_app.cpp
apps/soundboard_app.cpp
apps/ui_recon.cpp
apps/ui_recon_settings.cpp
apps/ui_level.cpp
apps/tpms_app.cpp
apps/tpms_app.cpp
apps/ui_spectrum_painter.cpp
apps/ui_spectrum_painter_image.cpp
apps/ui_spectrum_painter_text.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
protocols/dcs.cpp
protocols/encoders.cpp
protocols/lcr.cpp
# protocols/lcr.cpp
protocols/modems.cpp
protocols/rds.cpp
# ui_handwrite.cpp
@@ -300,9 +341,14 @@ set(CPPSRC
# ui_replay_view.cpp
# ui_script.cpp
ui_sd_card_debug.cpp
config_mode.cpp
${CPLD_20150901_DATA_CPP}
${CPLD_20170522_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
view_factory_base.cpp
${EXTCPPSRC}
)
# C sources to be compiled in ARM mode regardless of the global setting.
@@ -335,6 +381,9 @@ set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC
${HALINC} ${PLATFORMINC} ${BOARDINC}
${FATFSINC}
${CHIBIOS}/os/various
${HACKRF_PATH}/firmware/libopencm3/include
${HACKRF_PATH}/firmware/common
${HACKRF_PATH}/firmware
ui
hw
apps
@@ -378,7 +427,7 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
# List all default C defines here, like -D_DEBUG=1
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"'")
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5} -D'GCC_VERSION_MISMATCH=${GCC_VERSION_MISMATCH}'")
# List all default ASM defines here, like -D_DEBUG=1
set(DADEFS)
@@ -447,12 +496,13 @@ set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
add_definitions(${DEFS})
include_directories(. ${INCDIR})
link_directories(${LLIBDIR})
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
target_link_libraries(${PROJECT_NAME}.elf ${LIBS} "-L${CMAKE_CURRENT_LIST_DIR}/external")
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
add_custom_command(
OUTPUT ${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
COMMAND ${CMAKE_OBJCOPY} -v -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin --remove-section=.external_app_*
COMMAND ${EXPORT_EXTERNAL_APP_IMAGES} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_OBJCOPY} ${EXTAPPLIST}
DEPENDS ${PROJECT_NAME}.elf
)
+265 -72
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -23,89 +24,281 @@
#include "app_settings.hpp"
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include <cstring>
#include "utility.hpp"
#include <algorithm>
#include <cstring>
#include <string_view>
namespace std {
namespace fs = std::filesystem;
using namespace portapack;
int app_settings::load(std::string application, AppSettings* settings) {
if (portapack::persistent_memory::load_app_settings()) {
file_path = folder + "/" + application + ".ini";
namespace {
fs::path get_settings_path(const std::string& app_name) {
return fs::path{SETTINGS_DIR} / app_name + u".ini";
}
} // namespace
auto error = settings_file.open(file_path);
if (!error.is_valid()) {
auto error = settings_file.read(file_content, std::min((int)settings_file.size(), MAX_FILE_CONTENT_SIZE));
settings->baseband_bandwidth = std::app_settings::read_long_long(file_content, "baseband_bandwidth=");
settings->channel_bandwidth = std::app_settings::read_long_long(file_content, "channel_bandwidth=");
settings->lna = std::app_settings::read_long_long(file_content, "lna=");
settings->modulation = std::app_settings::read_long_long(file_content, "modulation=");
settings->rx_amp = std::app_settings::read_long_long(file_content, "rx_amp=");
settings->rx_frequency = std::app_settings::read_long_long(file_content, "rx_frequency=");
settings->sampling_rate = std::app_settings::read_long_long(file_content, "sampling_rate=");
settings->vga = std::app_settings::read_long_long(file_content, "vga=");
settings->tx_amp = std::app_settings::read_long_long(file_content, "tx_amp=");
settings->tx_frequency = std::app_settings::read_long_long(file_content, "tx_frequency=");
settings->tx_gain = std::app_settings::read_long_long(file_content, "tx_gain=");
settings->step = std::app_settings::read_long_long(file_content, "step=");
settings->modulation = std::app_settings::read_long_long(file_content, "modulation=");
settings->am_config_index = std::app_settings::read_long_long(file_content, "am_config_index=");
settings->nbfm_config_index = std::app_settings::read_long_long(file_content, "nbfm_config_index=");
settings->wfm_config_index = std::app_settings::read_long_long(file_content, "wfm_config_index=");
settings->squelch = std::app_settings::read_long_long(file_content, "squelch=");
rc = SETTINGS_OK;
} else
rc = SETTINGS_UNABLE_TO_LOAD;
} else
rc = SETTINGS_DISABLED;
return (rc);
void BoundSetting::parse(std::string_view value) {
switch (type_) {
case SettingType::I64:
parse_int(value, as<int64_t>());
break;
case SettingType::I32:
parse_int(value, as<int32_t>());
break;
case SettingType::U32:
parse_int(value, as<uint32_t>());
break;
case SettingType::U8:
parse_int(value, as<uint8_t>());
break;
case SettingType::String:
as<std::string>() = trim(value);
break;
case SettingType::Bool: {
int parsed = 0;
parse_int(value, parsed);
as<bool>() = (parsed != 0);
break;
}
};
}
int app_settings::save(std::string application, AppSettings* settings) {
if (portapack::persistent_memory::save_app_settings()) {
file_path = folder + "/" + application + ".ini";
make_new_directory(folder);
void BoundSetting::write(File& file) const {
// NB: Write directly without allocations. This happens on every
// app exit when enabled so should be fast to keep the UX responsive.
StringFormatBuffer buffer;
size_t length = 0;
auto error = settings_file.create(file_path);
if (!error.is_valid()) {
// Save common setting
settings_file.write_line("baseband_bandwidth=" + to_string_dec_uint(portapack::receiver_model.baseband_bandwidth()));
settings_file.write_line("channel_bandwidth=" + to_string_dec_uint(portapack::transmitter_model.channel_bandwidth()));
settings_file.write_line("lna=" + to_string_dec_uint(portapack::receiver_model.lna()));
settings_file.write_line("rx_amp=" + to_string_dec_uint(portapack::receiver_model.rf_amp()));
settings_file.write_line("sampling_rate=" + to_string_dec_uint(portapack::receiver_model.sampling_rate()));
settings_file.write_line("tx_amp=" + to_string_dec_uint(portapack::transmitter_model.rf_amp()));
settings_file.write_line("tx_gain=" + to_string_dec_uint(portapack::transmitter_model.tx_gain()));
settings_file.write_line("vga=" + to_string_dec_uint(portapack::receiver_model.vga()));
// Save other settings from struct
settings_file.write_line("rx_frequency=" + to_string_dec_uint(settings->rx_frequency));
settings_file.write_line("tx_frequency=" + to_string_dec_uint(settings->tx_frequency));
settings_file.write_line("step=" + to_string_dec_uint(settings->step));
settings_file.write_line("modulation=" + to_string_dec_uint(settings->modulation));
settings_file.write_line("am_config_index=" + to_string_dec_uint(settings->am_config_index));
settings_file.write_line("nbfm_config_index=" + to_string_dec_uint(settings->nbfm_config_index));
settings_file.write_line("wfm_config_index=" + to_string_dec_uint(settings->wfm_config_index));
settings_file.write_line("squelch=" + to_string_dec_uint(settings->squelch));
file.write(name_.data(), name_.length());
file.write("=", 1);
rc = SETTINGS_OK;
} else
rc = SETTINGS_UNABLE_TO_SAVE;
} else
rc = SETTINGS_DISABLED;
return (rc);
}
long long int app_settings::read_long_long(char* file_content, const char* setting_text) {
auto position = strstr(file_content, (char*)setting_text);
if (position) {
position += strlen((char*)setting_text);
setting_value = strtoll(position, nullptr, 10);
switch (type_) {
case SettingType::I64:
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
break;
case SettingType::I32:
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
break;
case SettingType::U32:
file.write(to_string_dec_uint(as<uint32_t>(), buffer, length), length);
break;
case SettingType::U8:
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
break;
case SettingType::String: {
const auto& str = as<std::string>();
file.write(str.data(), str.length());
break;
}
case SettingType::Bool:
file.write(as<bool>() ? "1" : "0", 1);
break;
}
return (setting_value);
file.write("\r\n", 2);
}
} /* namespace std */
SettingsStore::SettingsStore(std::string_view store_name, SettingBindings bindings)
: store_name_{store_name}, bindings_{bindings} {
reload();
}
SettingsStore::~SettingsStore() {
save();
}
void SettingsStore::reload() {
load_settings(store_name_, bindings_);
}
void SettingsStore::save() const {
save_settings(store_name_, bindings_);
}
bool load_settings(std::string_view store_name, SettingBindings& bindings) {
File f;
auto path = get_settings_path(std::string{store_name});
auto error = f.open(path);
if (error)
return false;
auto reader = FileLineReader(f);
for (const auto& line : reader) {
auto cols = split_string(line, '=');
if (cols.size() != 2)
continue;
// Find a binding with the name.
auto it = std::find_if(
bindings.begin(), bindings.end(),
[name = cols[0]](auto& bound_setting) {
return name == bound_setting.name();
});
// If found, parse the value.
if (it != bindings.end())
it->parse(cols[1]);
}
return true;
}
bool save_settings(std::string_view store_name, const SettingBindings& bindings) {
File f;
auto path = get_settings_path(std::string{store_name});
ensure_directory(SETTINGS_DIR);
auto error = f.create(path);
if (error)
return false;
for (const auto& bound_setting : bindings)
bound_setting.write(f);
return true;
}
namespace app_settings {
void copy_to_radio_model(const AppSettings& settings) {
// NB: Don't actually adjust the radio here or it may hang.
// Specifically 'modulation' which requires a running baseband.
if (flags_enabled(settings.mode, Mode::TX)) {
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
persistent_memory::set_target_frequency(settings.tx_frequency);
transmitter_model.configure_from_app_settings(settings);
}
if (flags_enabled(settings.mode, Mode::RX)) {
if (!flags_enabled(settings.options, Options::UseGlobalTargetFrequency))
persistent_memory::set_target_frequency(settings.rx_frequency);
receiver_model.configure_from_app_settings(settings);
receiver_model.set_configuration_without_update(
static_cast<ReceiverModel::Mode>(settings.modulation),
settings.step,
settings.am_config_index,
settings.nbfm_config_index,
settings.wfm_config_index,
settings.squelch);
}
receiver_model.set_frequency_step(settings.step);
receiver_model.set_normalized_headphone_volume(settings.volume);
}
void copy_from_radio_model(AppSettings& settings) {
if (flags_enabled(settings.mode, Mode::TX)) {
settings.tx_frequency = transmitter_model.target_frequency();
settings.baseband_bandwidth = transmitter_model.baseband_bandwidth();
settings.sampling_rate = transmitter_model.sampling_rate();
settings.tx_amp = transmitter_model.rf_amp();
settings.tx_gain = transmitter_model.tx_gain();
settings.channel_bandwidth = transmitter_model.channel_bandwidth();
}
if (flags_enabled(settings.mode, Mode::RX)) {
settings.rx_frequency = receiver_model.target_frequency();
settings.baseband_bandwidth = receiver_model.baseband_bandwidth();
settings.sampling_rate = receiver_model.sampling_rate();
settings.lna = receiver_model.lna();
settings.vga = receiver_model.vga();
settings.rx_amp = receiver_model.rf_amp();
settings.squelch = receiver_model.squelch_level();
settings.modulation = static_cast<uint8_t>(receiver_model.modulation());
settings.am_config_index = receiver_model.am_configuration();
settings.nbfm_config_index = receiver_model.nbfm_configuration();
settings.wfm_config_index = receiver_model.wfm_configuration();
}
settings.step = receiver_model.frequency_step();
settings.volume = receiver_model.normalized_headphone_volume();
}
/* SettingsManager *************************************************/
SettingsManager::SettingsManager(std::string_view app_name, Mode mode, Options options)
: SettingsManager{app_name, mode, options, {}} {}
SettingsManager::SettingsManager(
std::string_view app_name,
Mode mode,
SettingBindings additional_settings)
: SettingsManager{app_name, mode, Options::None, std::move(additional_settings)} {}
SettingsManager::SettingsManager(
std::string_view app_name,
Mode mode,
Options options,
SettingBindings additional_settings)
: app_name_{app_name},
settings_{},
bindings_{},
loaded_{false} {
settings_.mode = mode;
settings_.options = options;
// Pre-alloc enough for app settings and additional settings.
additional_settings.reserve(COMMON_APP_SETTINGS_COUNT + additional_settings.size());
bindings_ = std::move(additional_settings);
// Settings should always be loaded because apps now rely
// on being able to store UI settings, config, etc.
// Transmitter model settings.
if (flags_enabled(mode, Mode::TX)) {
bindings_.emplace_back("tx_frequency"sv, &settings_.tx_frequency);
bindings_.emplace_back("tx_amp"sv, &settings_.tx_amp);
bindings_.emplace_back("tx_gain"sv, &settings_.tx_gain);
bindings_.emplace_back("channel_bandwidth"sv, &settings_.channel_bandwidth);
}
// Receiver model settings.
if (flags_enabled(mode, Mode::RX)) {
bindings_.emplace_back("rx_frequency"sv, &settings_.rx_frequency);
bindings_.emplace_back("lna"sv, &settings_.lna);
bindings_.emplace_back("vga"sv, &settings_.vga);
bindings_.emplace_back("rx_amp"sv, &settings_.rx_amp);
bindings_.emplace_back("modulation"sv, &settings_.modulation);
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
// Common model settings.
bindings_.emplace_back("baseband_bandwidth"sv, &settings_.baseband_bandwidth);
bindings_.emplace_back("sampling_rate"sv, &settings_.sampling_rate);
bindings_.emplace_back("step"sv, &settings_.step);
bindings_.emplace_back("volume"sv, &settings_.volume);
// RadioState should have initialized default radio parameters before this function;
// copy them to settings_ before checking settings .ini file (in case the file doesn't exist
// or doesn't include all parameters). Settings in the file can overwrite all, or a subset of parameters.
copy_from_radio_model(settings_);
loaded_ = load_settings(app_name_, bindings_);
// Only copy to the radio if load was successful.
if (loaded_)
copy_to_radio_model(settings_);
}
SettingsManager::~SettingsManager() {
copy_from_radio_model(settings_);
save_settings(app_name_, bindings_);
}
} // namespace app_settings
+151 -36
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -27,53 +28,167 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include "file.hpp"
#include "max283x.hpp"
#include "string_format.hpp"
namespace std {
class app_settings {
public:
#define SETTINGS_OK 0 // settings found
#define SETTINGS_UNABLE_TO_LOAD -1 // settings (file) not found
#define SETTINGS_UNABLE_TO_SAVE -2 // unable to save settings
#define SETTINGS_DISABLED -3 // load/save settings disabled in settings
// Folder to store app settings, pmem_fileflag, and date_fileflag
#define SETTINGS_DIR u"/SETTINGS"
struct AppSettings {
uint32_t baseband_bandwidth;
uint32_t channel_bandwidth;
uint8_t lna;
uint8_t modulation;
uint8_t rx_amp;
uint32_t rx_frequency;
uint32_t sampling_rate;
uint8_t tx_amp;
uint32_t tx_frequency;
uint8_t tx_gain;
uint8_t vga;
uint32_t step;
uint8_t am_config_index;
uint8_t nbfm_config_index;
uint8_t wfm_config_index;
uint8_t squelch;
// Bring in the string_view literal.
using std::literals::operator""sv;
#define COMMON_APP_SETTINGS_COUNT 19
/* Represents a named setting bound to a variable instance. */
/* Using void* instead of std::variant, because variant is a pain to dispatch over. */
class BoundSetting {
/* The type of bound setting. */
enum class SettingType : uint8_t {
I64,
I32,
U32,
U8,
String,
Bool,
};
int load(std::string application, AppSettings* settings);
int save(std::string application, AppSettings* settings);
public:
BoundSetting(std::string_view name, int64_t* target)
: name_{name}, target_{target}, type_{SettingType::I64} {}
BoundSetting(std::string_view name, int32_t* target)
: name_{name}, target_{target}, type_{SettingType::I32} {}
BoundSetting(std::string_view name, uint32_t* target)
: name_{name}, target_{target}, type_{SettingType::U32} {}
BoundSetting(std::string_view name, uint8_t* target)
: name_{name}, target_{target}, type_{SettingType::U8} {}
BoundSetting(std::string_view name, std::string* target)
: name_{name}, target_{target}, type_{SettingType::String} {}
BoundSetting(std::string_view name, bool* target)
: name_{name}, target_{target}, type_{SettingType::Bool} {}
std::string_view name() const { return name_; }
void parse(std::string_view value);
void write(File& file) const;
private:
#define MAX_FILE_CONTENT_SIZE 1000
template <typename T>
constexpr auto& as() const {
return *reinterpret_cast<T*>(target_);
}
char file_content[MAX_FILE_CONTENT_SIZE] = {};
std::string file_path = "";
std::string folder = "SETTINGS";
int rc = SETTINGS_OK;
File settings_file{};
long long int setting_value{};
std::string_view name_;
void* target_;
SettingType type_;
};
long long int read_long_long(char* file_content, const char* setting_text);
using SettingBindings = std::vector<BoundSetting>;
}; // class app_settings
} // namespace std
/* RAII wrapper for Settings that loads/saves to the SD card. */
class SettingsStore {
public:
SettingsStore(std::string_view store_name, SettingBindings bindings);
~SettingsStore();
void reload();
void save() const;
private:
std::string_view store_name_;
SettingBindings bindings_;
};
bool load_settings(std::string_view store_name, SettingBindings& bindings);
bool save_settings(std::string_view store_name, const SettingBindings& bindings);
namespace app_settings {
enum class Mode : uint8_t {
RX = 0x01,
TX = 0x02,
RX_TX = 0x03, // Both TX/RX
};
enum class Options {
None = 0x0000,
/* Don't use target frequency from app settings. */
UseGlobalTargetFrequency = 0x0001,
};
/* NB: See RX/TX model headers for default values. */
struct AppSettings {
Mode mode = Mode::RX;
Options options = Options::None;
uint32_t baseband_bandwidth = max283x::filter::bandwidth_minimum;
uint32_t sampling_rate = 3072000; // Good for 48k audio.
uint8_t lna = 32;
uint8_t vga = 32;
uint8_t rx_amp = 0;
uint8_t tx_amp = 0;
uint8_t tx_gain = 35;
uint32_t channel_bandwidth = 1;
uint32_t rx_frequency;
uint32_t tx_frequency;
uint32_t step = 25000;
uint8_t modulation = 1; // NFM
uint8_t am_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t squelch = 80;
uint8_t volume;
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
};
/* Copies common values to the receiver/transmitter models. */
void copy_to_radio_model(const AppSettings& settings);
/* Copies common values from the receiver/transmitter models. */
void copy_from_radio_model(AppSettings& settings);
/* RAII wrapper for automatically loading and saving settings for an app.
* "Additional" settings are always loaded, but radio settings are conditionally
* saved/loaded if the global app settings options are enabled.
* NB: This should be added to a class before any LNA/VGA controls so that
* the receiver/transmitter models are set before the control ctors run. */
class SettingsManager {
public:
SettingsManager(std::string_view app_name, Mode mode, Options options = Options::None);
SettingsManager(std::string_view app_name, Mode mode, SettingBindings additional_settings);
SettingsManager(std::string_view app_name, Mode mode, Options options, SettingBindings additional_settings);
~SettingsManager();
SettingsManager(const SettingsManager&) = delete;
SettingsManager& operator=(const SettingsManager&) = delete;
SettingsManager(SettingsManager&&) = delete;
SettingsManager& operator=(SettingsManager&&) = delete;
/* True if settings were successfully loaded from file. */
bool loaded() const { return loaded_; }
Mode mode() const { return settings_.mode; }
AppSettings& raw() { return settings_; }
private:
std::string_view app_name_;
AppSettings settings_;
SettingBindings bindings_;
bool loaded_;
};
} // namespace app_settings
ENABLE_FLAGS_OPERATORS(app_settings::Mode);
ENABLE_FLAGS_OPERATORS(app_settings::Options);
#endif /*__APP_SETTINGS_H__*/
+3 -33
View File
@@ -55,12 +55,8 @@ void ACARSLogger::log_raw_data(const acars::Packet& packet, const uint32_t frequ
namespace ui {
void ACARSAppView::update_freq(rf::Frequency f) {
set_target_frequency(f);
portapack::persistent_memory::set_tuned_frequency(f); // Maybe not ?
}
ACARSAppView::ACARSAppView(NavigationView& nav) {
ACARSAppView::ACARSAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_acars);
add_children({&rssi,
@@ -72,25 +68,8 @@ ACARSAppView::ACARSAppView(NavigationView& nav) {
&check_log,
&console});
receiver_model.set_sampling_rate(2457600);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
field_frequency.set_value(receiver_model.tuning_frequency());
update_freq(receiver_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
@@ -132,16 +111,7 @@ void ACARSAppView::on_packet(const acars::Packet& packet) {
// Log raw data whatever it contains
if (logger && logging)
logger->log_raw_data(packet, target_frequency());
}
void ACARSAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
receiver_model.set_tuning_frequency(new_value);
}
uint32_t ACARSAppView::target_frequency() const {
return target_frequency_;
logger->log_raw_data(packet, receiver_model.target_frequency());
}
} /* namespace ui */
+16 -14
View File
@@ -23,10 +23,12 @@
#ifndef __ACARS_APP_H__
#define __ACARS_APP_H__
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_rssi.hpp"
#include "log_file.hpp"
#include "acars_packet.hpp"
@@ -56,6 +58,15 @@ class ACARSAppView : public View {
std::string title() const override { return "ACARS (WIP)"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{
131550000 /* frequency */,
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_acars", app_settings::Mode::RX};
bool logging{false};
uint32_t packet_counter{0};
@@ -66,15 +77,13 @@ class ACARSAppView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
};
nav_};
Checkbox check_log{
{22 * 8, 21},
3,
@@ -86,15 +95,8 @@ class ACARSAppView : public View {
std::unique_ptr<ACARSLogger> logger{};
uint32_t target_frequency_{};
void update_freq(rf::Frequency f);
void on_packet(const acars::Packet& packet);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
MessageHandlerRegistration message_handler_packet{
Message::ID::ACARSPacket,
[this](Message* const p) {
+10 -44
View File
@@ -283,8 +283,8 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
});
button_done.on_select = [this](const ui::Button&) {
if (this->on_close) {
this->on_close();
if (on_close) {
on_close();
}
};
@@ -293,6 +293,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
ais::format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::NONE,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
@@ -315,7 +316,7 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
if (send_updates)
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0);
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
}
void AISRecentEntryDetailView::focus() {
@@ -380,33 +381,20 @@ AISAppView::AISAppView(NavigationView& nav)
&recent_entry_detail_view,
});
// load app settings
auto rc = settings.load("rx_ais", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
target_frequency_ = app_settings.rx_frequency;
} else
target_frequency_ = initial_target_frequency;
recent_entry_detail_view.hidden(true);
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
receiver_model.enable();
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
this->on_frequency_changed(v);
receiver_model.set_target_frequency(v);
};
options_channel.set_by_value(target_frequency());
options_channel.set_by_value(receiver_model.target_frequency());
recent_entries_view.on_select = [this](const AISRecentEntry& entry) {
this->on_show_detail(entry);
on_show_detail(entry);
};
recent_entry_detail_view.on_close = [this]() {
this->on_show_list();
on_show_list();
};
logger = std::make_unique<AISLogger>();
@@ -416,12 +404,7 @@ AISAppView::AISAppView(NavigationView& nav)
}
AISAppView::~AISAppView() {
// save app settings
app_settings.rx_frequency = target_frequency_;
settings.save("rx_ais", &app_settings);
receiver_model.disable(); // to switch off all, including DC bias.
receiver_model.disable();
baseband::shutdown();
}
@@ -465,21 +448,4 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.focus();
}
void AISAppView::on_frequency_changed(const uint32_t new_target_frequency) {
set_target_frequency(new_target_frequency);
}
void AISAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
radio::set_tuning_frequency(tuning_frequency());
}
uint32_t AISAppView::target_frequency() const {
return target_frequency_;
}
uint32_t AISAppView::tuning_frequency() const {
return target_frequency() - (sampling_rate / 4);
}
} /* namespace ui */
+8 -19
View File
@@ -34,6 +34,7 @@
#include "log_file.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ais_packet.hpp"
#include "lpc43xx_cpp.hpp"
@@ -154,9 +155,6 @@ class AISAppView : public View {
~AISAppView();
void set_parent_rect(const Rect new_parent_rect) override;
// Prevent painting of region covered entirely by a child.
// TODO: Add flag to View that specifies view does not need to be cleared before painting.
void paint(Painter&) override{};
void focus() override;
@@ -164,13 +162,13 @@ class AISAppView : public View {
std::string title() const override { return "AIS RX"; };
private:
static constexpr uint32_t initial_target_frequency = 162025000;
static constexpr uint32_t sampling_rate = 2457600;
static constexpr uint32_t baseband_bandwidth = 1750000;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
RxRadioState radio_state_{
162025000 /* frequency*/,
1750000 /* bandwidth */,
2457600 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_ais", app_settings::Mode::RX};
NavigationView& nav_;
@@ -225,18 +223,9 @@ class AISAppView : public View {
}
}};
uint32_t target_frequency_ = initial_target_frequency;
void on_packet(const ais::Packet& packet);
void on_show_list();
void on_show_detail(const AISRecentEntry& entry);
void on_frequency_changed(const uint32_t new_target_frequency);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
uint32_t tuning_frequency() const;
};
} /* namespace ui */
+59 -123
View File
@@ -22,29 +22,27 @@
#include "analog_audio_app.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace tonekey;
#include "audio.hpp"
#include "file.hpp"
#include "string_format.hpp"
#include "ui_freqman.hpp"
#include "utility.hpp"
#include "string_format.hpp"
#include "freqman.hpp"
using namespace portapack;
using namespace tonekey;
namespace ui {
/* AMOptionsView *********************************************************/
static const Style& style_options_group = Styles::bg_blue;
AMOptionsView::AMOptionsView(
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -54,8 +52,8 @@ AMOptionsView::AMOptionsView(
});
freqman_set_bandwidth_option(AM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.am_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.am_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_am_configuration(n);
};
}
@@ -63,8 +61,8 @@ AMOptionsView::AMOptionsView(
/* NBFMOptionsView *******************************************************/
NBFMOptionsView::NBFMOptionsView(
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -74,8 +72,8 @@ NBFMOptionsView::NBFMOptionsView(
&field_squelch});
freqman_set_bandwidth_option(NFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.nbfm_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.nbfm_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_nbfm_configuration(n);
};
@@ -88,8 +86,8 @@ NBFMOptionsView::NBFMOptionsView(
/* WFMOptionsView *******************************************************/
WFMOptionsView::WFMOptionsView(
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -99,8 +97,8 @@ WFMOptionsView::WFMOptionsView(
});
freqman_set_bandwidth_option(WFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_selected_index(receiver_model.wfm_configuration());
options_config.on_change = [this](size_t n, OptionsField::value_t) {
options_config.set_by_value(receiver_model.wfm_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_wfm_configuration(n);
};
}
@@ -109,8 +107,8 @@ WFMOptionsView::WFMOptionsView(
SPECOptionsView::SPECOptionsView(
AnalogAudioView* view,
const Rect parent_rect,
const Style* const style)
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
@@ -135,6 +133,9 @@ SPECOptionsView::SPECOptionsView(
AnalogAudioView::AnalogAudioView(
NavigationView& nav)
: nav_(nav) {
// A baseband image _must_ be running before add waterfall view.
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({&rssi,
&channel,
&audio,
@@ -147,35 +148,9 @@ AnalogAudioView::AnalogAudioView(
&record_view,
&waterfall});
// Set on_change before initialising the field
field_frequency.on_change = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
};
// load app settings
auto rc = settings.load("rx_audio", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
receiver_model.set_rf_amp(app_settings.rx_amp);
// field_frequency.set_value(app_settings.rx_frequency);
receiver_model.set_configuration_without_init(static_cast<ReceiverModel::Mode>(app_settings.modulation), app_settings.step, app_settings.am_config_index, app_settings.nbfm_config_index, app_settings.wfm_config_index, app_settings.squelch);
}
field_frequency.set_value(receiver_model.tuning_frequency());
// Filename Datetime and Frequency
record_view.set_filename_date_frequency(true);
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
field_frequency.on_show_options = [this]() {
this->on_show_options_frequency();
};
@@ -188,9 +163,12 @@ AnalogAudioView::AnalogAudioView(
this->on_show_options_rf_gain();
};
const auto modulation = receiver_model.modulation();
options_modulation.set_by_value(toUType(modulation));
auto modulation = receiver_model.modulation();
// This app doesn't handle "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
options_modulation.set_by_value(toUType(modulation));
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
this->on_modulation_changed(static_cast<ReceiverModel::Mode>(v));
};
@@ -198,23 +176,28 @@ AnalogAudioView::AnalogAudioView(
this->on_show_options_modulation();
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
waterfall.on_select = [this](int32_t offset) {
field_frequency.set_value(receiver_model.tuning_frequency() + offset);
field_frequency.set_value(receiver_model.target_frequency() + offset);
};
audio::output::start();
update_modulation(static_cast<ReceiverModel::Mode>(modulation));
on_modulation_changed(static_cast<ReceiverModel::Mode>(modulation));
// This call starts the correct baseband image to run
// and sets the radio up as necessary for the given modulation.
on_modulation_changed(modulation);
}
AnalogAudioView::AnalogAudioView(
NavigationView& nav,
ReceiverModel::settings_t override)
: AnalogAudioView(nav) {
// TODO: Which other settings make sense to override?
on_frequency_step_changed(override.frequency_step);
options_modulation.set_by_value(toUType(override.mode));
}
size_t AnalogAudioView::get_spec_bw_index() {
@@ -241,40 +224,15 @@ void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
}
AnalogAudioView::~AnalogAudioView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
app_settings.lna = receiver_model.lna();
app_settings.vga = receiver_model.vga();
app_settings.rx_amp = receiver_model.rf_amp();
app_settings.step = receiver_model.frequency_step();
app_settings.modulation = (uint8_t)receiver_model.modulation();
app_settings.am_config_index = receiver_model.am_configuration();
app_settings.nbfm_config_index = receiver_model.nbfm_configuration();
app_settings.wfm_config_index = receiver_model.wfm_configuration();
app_settings.squelch = receiver_model.squelch_level();
settings.save("rx_audio", &app_settings);
// TODO: Manipulating audio codec here, and in ui_receiver.cpp. Good to do
// both?
audio::output::stop();
receiver_model.set_sampling_rate(3072000); // Just a hack to avoid hanging other apps if the last modulation was SPEC
receiver_model.disable();
baseband::shutdown();
}
void AnalogAudioView::on_hide() {
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
waterfall.on_hide();
View::on_hide();
}
void AnalogAudioView::set_parent_rect(const Rect new_parent_rect) {
void AnalogAudioView::set_parent_rect(Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
@@ -282,21 +240,19 @@ void AnalogAudioView::focus() {
field_frequency.focus();
}
void AnalogAudioView::on_tuning_frequency_changed(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
receiver_model.set_baseband_bandwidth(bandwidth_hz);
}
void AnalogAudioView::on_modulation_changed(const ReceiverModel::Mode modulation) {
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
waterfall.on_hide();
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
// This app doesn't know what to do with "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
baseband::spectrum_streaming_stop();
update_modulation(modulation);
on_show_options_modulation();
waterfall.on_show();
baseband::spectrum_streaming_start();
}
void AnalogAudioView::remove_options_widget() {
@@ -348,7 +304,7 @@ void AnalogAudioView::on_show_options_rf_gain() {
void AnalogAudioView::on_show_options_modulation() {
std::unique_ptr<Widget> widget;
const auto modulation = static_cast<ReceiverModel::Mode>(receiver_model.modulation());
const auto modulation = receiver_model.modulation();
switch (modulation) {
case ReceiverModel::Mode::AMAudio:
widget = std::make_unique<AMOptionsView>(options_view_rect, &style_options_group);
@@ -375,6 +331,7 @@ void AnalogAudioView::on_show_options_modulation() {
break;
default:
chDbgPanic("Unhandled Mode");
break;
}
@@ -391,12 +348,7 @@ void AnalogAudioView::on_reference_ppm_correction_changed(int32_t v) {
persistent_memory::set_correction_ppb(v * 1000);
}
void AnalogAudioView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
}
void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
audio::output::mute();
record_view.stop();
@@ -417,7 +369,8 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
image_tag = portapack::spi_flash::image_tag_wideband_spectrum;
break;
default:
return;
chDbgPanic("Unhandled Mode");
break;
}
baseband::run_image(image_tag);
@@ -456,25 +409,8 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
}
}
void AnalogAudioView::handle_coded_squelch(const uint32_t value) {
float diff, min_diff = value;
size_t min_idx{0};
size_t c;
// Find nearest match
for (c = 0; c < tone_keys.size(); c++) {
diff = abs(((float)value / 100.0) - tone_keys[c].second);
if (diff < min_diff) {
min_idx = c;
min_diff = diff;
}
}
// Arbitrary confidence threshold
if (min_diff < 40)
text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
else
text_ctcss.set("???");
void AnalogAudioView::handle_coded_squelch(uint32_t value) {
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
}
} /* namespace ui */
+35 -62
View File
@@ -26,23 +26,19 @@
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_spectrum.hpp"
#include "ui_record_view.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "tone_key.hpp"
namespace ui {
constexpr Style style_options_group{
.font = font::fixed_8x16,
.background = Color::blue(),
.foreground = Color::white(),
};
class AMOptionsView : public View {
public:
AMOptionsView(const Rect parent_rect, const Style* const style);
AMOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -52,15 +48,15 @@ class AMOptionsView : public View {
OptionsField options_config{
{3 * 8, 0 * 16},
5,
6, // Max option length
{
// using common messages from freqman.cpp
// Using common messages from freqman_ui.cpp
}};
};
class NBFMOptionsView : public View {
public:
NBFMOptionsView(const Rect parent_rect, const Style* const style);
NBFMOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -69,16 +65,16 @@ class NBFMOptionsView : public View {
};
OptionsField options_config{
{3 * 8, 0 * 16},
4,
3, // Max option length
{
// using common messages from freqman.cpp
// Using common messages from freqman_ui.cpp
}};
Text text_squelch{
{9 * 8, 0 * 16, 8 * 8, 1 * 16},
{7 * 8, 0 * 16, 8 * 8, 1 * 16},
"SQ /99"};
NumberField field_squelch{
{12 * 8, 0 * 16},
{10 * 8, 0 * 16},
2,
{0, 99},
1,
@@ -88,7 +84,7 @@ class NBFMOptionsView : public View {
class WFMOptionsView : public View {
public:
WFMOptionsView(const Rect parent_rect, const Style* const style);
WFMOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -97,9 +93,9 @@ class WFMOptionsView : public View {
};
OptionsField options_config{
{3 * 8, 0 * 16},
4,
4, // Max option length
{
// using common messages from freqman.cpp
// Using common messages from freqman_ui.cpp
}};
};
@@ -107,7 +103,7 @@ class AnalogAudioView;
class SPECOptionsView : public View {
public:
SPECOptionsView(AnalogAudioView* view, const Rect parent_rect, const Style* const style);
SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
private:
Text label_config{
@@ -142,12 +138,10 @@ class SPECOptionsView : public View {
class AnalogAudioView : public View {
public:
AnalogAudioView(NavigationView& nav);
AnalogAudioView(NavigationView& nav, ReceiverModel::settings_t override);
~AnalogAudioView();
void on_hide() override;
void set_parent_rect(const Rect new_parent_rect) override;
void set_parent_rect(Rect new_parent_rect) override;
void focus() override;
std::string title() const override { return "Audio RX"; };
@@ -161,9 +155,11 @@ class AnalogAudioView : public View {
private:
static constexpr ui::Dim header_height = 3 * 16;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_audio", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
@@ -172,24 +168,18 @@ class AnalogAudioView : public View {
uint32_t spec_bw = 20000000;
uint16_t spec_trigger = 63;
NavigationView& nav_;
// bool exit_on_squelch { false };
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
Audio audio{
{21 * 8, 10, 6 * 8, 4},
};
{21 * 8, 10, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{5 * 8, 0 * 16},
};
nav_};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
@@ -207,16 +197,11 @@ class AnalogAudioView : public View {
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
}};
NumberField field_volume{
{28 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Text text_ctcss{
{19 * 8, 1 * 16, 11 * 8, 1 * 16},
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
""};
std::unique_ptr<Widget> options_widget{};
@@ -229,38 +214,26 @@ class AnalogAudioView : public View {
4096,
4};
spectrum::WaterfallWidget waterfall{true};
spectrum::WaterfallView waterfall{true};
void on_tuning_frequency_changed(rf::Frequency f);
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
void on_modulation_changed(const ReceiverModel::Mode modulation);
void on_modulation_changed(ReceiverModel::Mode modulation);
void on_show_options_frequency();
void on_show_options_rf_gain();
void on_show_options_modulation();
void on_frequency_step_changed(rf::Frequency f);
void on_reference_ppm_correction_changed(int32_t v);
void on_headphone_volume_changed(int32_t v);
void on_edit_frequency();
void remove_options_widget();
void set_options_widget(std::unique_ptr<Widget> new_widget);
void update_modulation(const ReceiverModel::Mode modulation);
void update_modulation(ReceiverModel::Mode modulation);
// void squelched();
void handle_coded_squelch(const uint32_t value);
/*MessageHandlerRegistration message_handler_squelch_signal {
Message::ID::RequestSignal,
[this](const Message* const p) {
(void)p;
this->squelched();
}
};*/
void handle_coded_squelch(uint32_t value);
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
[this](const Message* const p) {
[this](const Message* p) {
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
this->handle_coded_squelch(message.value);
}};
+331
View File
@@ -0,0 +1,331 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 "ble_comm_app.hpp"
#include "ui_modemsetup.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_text.hpp"
using namespace portapack;
using namespace modems;
void BLECommLogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
namespace ui {
static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
void BLECommView::focus() {
options_channel.focus();
}
BLECommView::BLECommView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
&options_channel,
&field_frequency,
&label_send_adv,
&button_send_adv,
&check_log,
&label_packets_sent,
&text_packets_sent,
&console});
field_frequency.set_step(0);
button_send_adv.on_select = [this](ImageButton&) {
this->toggle();
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
logging = v;
if (logger && logging)
logger->append(LOG_ROOT_DIR "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
options_channel.on_change = [this](size_t, int32_t i) {
// If we selected Auto don't do anything and Auto will handle changing.
if (i == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number_rx = i;
channel_number_tx = i;
};
options_channel.set_selected_index(3, true);
logger = std::make_unique<BLECommLogger>();
// Generate new random Mac Address upon each new startup.
generateRandomMacAddress(randomMac);
// Setup Initial Advertise Packet.
advertisePacket = build_adv_packet();
}
void BLECommView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
console.set_parent_rect(content_rect);
}
BLECommView::~BLECommView() {
receiver_model.disable();
transmitter_model.disable();
baseband::shutdown();
}
bool BLECommView::in_tx_mode() const {
return (bool)is_running_tx;
}
void BLECommView::toggle() {
if (in_tx_mode()) {
sendAdvertisement(false);
} else {
sendAdvertisement(true);
}
}
BLETxPacket BLECommView::build_adv_packet() {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
std::string dataString = "11094861636b524620506f7274617061636b";
strncpy(bleTxPacket.macAddress, randomMac, 12);
strncpy(bleTxPacket.advertisementData, dataString.c_str(), dataString.length());
// Duty cycle of 40% per 100ms advertisment periods.
strncpy(bleTxPacket.packetCount, "80", 3);
bleTxPacket.packet_count = 80;
bleTxPacket.packetType = PKT_TYPE_DISCOVERY;
return bleTxPacket;
}
void BLECommView::sendAdvertisement(bool enable) {
if (enable) {
startTx(advertisePacket);
is_adv = true;
} else {
is_adv = false;
stopTx();
}
}
void BLECommView::startTx(BLETxPacket packetToSend) {
int randomChannel = channel_number_tx;
if (auto_channel) {
int min = 37;
int max = 39;
randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
}
if (!in_tx_mode()) {
switch_rx_tx(false);
currentPacket = packetToSend;
packet_counter = currentPacket.packet_count;
button_send_adv.set_bitmap(&bitmap_stop);
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
transmitter_model.set_tx_gain(47);
transmitter_model.enable();
is_running_tx = true;
} else {
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
}
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
is_sending = true;
packet_counter--;
}
void BLECommView::stopTx() {
button_send_adv.set_bitmap(&bitmap_play);
text_packets_sent.set(to_string_dec_uint(packet_counter));
switch_rx_tx(true);
baseband::set_btlerx(channel_number_rx);
receiver_model.enable();
is_running_tx = false;
}
void BLECommView::switch_rx_tx(bool inRxMode) {
if (inRxMode) {
// Start Rx
transmitter_model.disable();
baseband::shutdown();
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
} else {
// Start Tx
receiver_model.disable();
baseband::shutdown();
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
}
}
void BLECommView::on_data(BlePacketData* packet) {
parse_received_packet(packet, (ADV_PDU_TYPE)packet->type);
}
// called each 1/60th of second, so 6 = 100ms
void BLECommView::on_timer() {
// Send advertise burst only once every 100ms
if (++timer_counter == timer_period) {
timer_counter = 0;
if (!is_adv) {
sendAdvertisement(true);
}
}
}
void BLECommView::on_tx_progress(const bool done) {
if (done) {
if (in_tx_mode()) {
is_sending = false;
if (packet_counter == 0) {
if (is_adv) {
sendAdvertisement(false);
} else {
stopTx();
}
} else {
startTx(currentPacket);
}
}
}
}
void BLECommView::parse_received_packet(const BlePacketData* packet, ADV_PDU_TYPE pdu_type) {
std::string data_string;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
receivedPacket.dbValue = packet->max_dB;
receivedPacket.timestamp = to_string_timestamp(rtc_time::now());
receivedPacket.dataString = data_string;
receivedPacket.packetData.type = packet->type;
receivedPacket.packetData.size = packet->size;
receivedPacket.packetData.dataLen = packet->dataLen;
receivedPacket.packetData.macAddress[0] = packet->macAddress[0];
receivedPacket.packetData.macAddress[1] = packet->macAddress[1];
receivedPacket.packetData.macAddress[2] = packet->macAddress[2];
receivedPacket.packetData.macAddress[3] = packet->macAddress[3];
receivedPacket.packetData.macAddress[4] = packet->macAddress[4];
receivedPacket.packetData.macAddress[5] = packet->macAddress[5];
receivedPacket.numHits++;
for (int i = 0; i < packet->dataLen; i++) {
receivedPacket.packetData.data[i] = packet->data[i];
}
if (pdu_type == SCAN_REQ || pdu_type == CONNECT_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
console.clear(true);
std::string str_console = "";
std::string pduTypeStr = "";
if (pdu_type == SCAN_REQ) {
pduTypeStr += "SCAN_REQ";
} else if (pdu_type == CONNECT_REQ) {
pduTypeStr += "CONNECT_REQ";
}
str_console += "PACKET TYPE:" + pduTypeStr + "\n";
str_console += "MY MAC:" + to_string_formatted_mac_address(randomMac) + "\n";
str_console += "SCAN MAC:" + to_string_mac_address(directed_mac_data->A1, 6, false) + "\n";
console.write(str_console);
}
}
} /* namespace ui */
+203
View File
@@ -0,0 +1,203 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 __BLE_COMM_APP_H__
#define __BLE_COMM_APP_H__
#include "ble_rx_app.hpp"
#include "ble_tx_app.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_transmitter.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "recent_entries.hpp"
class BLECommLogger {
public:
Optional<File::Error> append(const std::string& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data);
private:
LogFile log_file{};
};
namespace ui {
class BLECommView : public View {
public:
BLECommView(NavigationView& nav);
~BLECommView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void focus() override;
std::string title() const override { return "BLE Comm"; };
private:
BLETxPacket build_adv_packet();
void switch_rx_tx(bool inRxMode);
void startTx(BLETxPacket packetToSend);
void stopTx();
void toggle();
bool in_tx_mode() const;
void on_timer();
void on_data(BlePacketData* packetData);
void on_tx_progress(const bool done);
void parse_received_packet(const BlePacketData* packet, ADV_PDU_TYPE pdu_type);
void sendAdvertisement(bool enable);
NavigationView& nav_;
RxRadioState radio_state_rx_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
TxRadioState radio_state_tx_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"BLE Comm Tx", app_settings::Mode::RX_TX};
uint8_t console_color{0};
uint32_t prev_value{0};
uint8_t channel_number_tx = 37;
uint8_t channel_number_rx = 37;
bool auto_channel = false;
char randomMac[13] = "010203040506";
bool is_running_tx = false;
bool is_sending = false;
bool is_adv = false;
int16_t timer_period{6}; // Delay each packet by 16ms.
int16_t timer_counter = 0;
int16_t timer_rx_counter = 0;
int16_t timer_rx_period{12}; // Poll Rx for at least 200ms. (TBD)
uint32_t packet_counter{0};
BLETxPacket advertisePacket{};
BLETxPacket currentPacket{};
BleRecentEntry receivedPacket{};
static constexpr auto header_height = 5 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
Labels label_send_adv{
{{0 * 8, 2 * 8}, "Send Advertisement:", Color::light_grey()}};
ImageButton button_send_adv{
{21 * 8, 1 * 16, 10 * 8, 2 * 16},
&bitmap_play,
Color::green(),
Color::black()};
Checkbox check_log{
{24 * 8, 2 * 8},
3,
"Log",
true};
Labels label_packets_sent{
{{0 * 8, 4 * 8}, "Packets Left:", Color::light_grey()}};
Text text_packets_sent{
{13 * 8, 2 * 16, 12 * 8, 16},
"-"};
Console console{
{0, 4 * 16, 240, 240}};
std::string str_log{""};
bool logging{false};
std::unique_ptr<BLECommLogger> logger{};
MessageHandlerRegistration message_handler_packet{
Message::ID::BlePacket,
[this](Message* const p) {
const auto message = static_cast<const BLEPacketMessage*>(p);
this->on_data(message->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);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
};
} /* namespace ui */
#endif /*__UI_AFSK_RX_H__*/
+936
View File
@@ -0,0 +1,936 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 "ble_rx_app.hpp"
#include "ble_rx_app.hpp"
#include "ui_modemsetup.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "io_file.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_textentry.hpp"
using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
void BLELogger::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
std::string pad_string_with_spaces(int snakes) {
std::string paddedStr(snakes, ' ');
return paddedStr;
}
uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
uint64_t result = 0;
// Copy each byte of the MAC address to the corresponding byte in the uint64_t.
for (int i = 0; i < 6; ++i) {
result |= static_cast<uint64_t>(macAddress[i]) << ((5 - i) * 8);
}
return result;
}
void reverse_byte_array(uint8_t* arr, int length) {
int start = 0;
int end = length - 1;
while (start < end) {
// Swap elements at start and end
uint8_t temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
// Move the indices towards the center
start++;
end--;
}
}
namespace ui {
std::string pdu_type_to_string(ADV_PDU_TYPE type) {
std::string pduTypeStr = "";
switch (type) {
case ADV_IND:
pduTypeStr += "ADV_IND";
break;
case ADV_DIRECT_IND:
pduTypeStr += "ADV_DIRECT_IND";
break;
case ADV_NONCONN_IND:
pduTypeStr += "ADV_NONCONN_IND";
break;
case SCAN_REQ:
pduTypeStr += "SCAN_REQ";
break;
case SCAN_RSP:
pduTypeStr += "SCAN_RSP";
break;
case CONNECT_REQ:
pduTypeStr += "CONNECT_REQ";
break;
case ADV_SCAN_IND:
pduTypeStr += "ADV_SCAN_IND";
break;
case RESERVED0:
case RESERVED1:
case RESERVED2:
case RESERVED3:
case RESERVED4:
case RESERVED5:
case RESERVED6:
case RESERVED7:
case RESERVED8:
pduTypeStr += "RESERVED";
break;
default:
pduTypeStr += "UNKNOWN";
break;
}
return pduTypeStr;
}
template <>
void RecentEntriesTable<BleRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
std::string line{};
line.reserve(30);
if (!entry.nameString.empty() && entry.include_name) {
line = entry.nameString;
if (line.length() < 17) {
line += pad_string_with_spaces(17 - line.length());
} else {
line = truncate(line, 17);
}
} else {
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
}
// Pushing single digit values down right justified.
std::string hitsStr = to_string_dec_int(entry.numHits);
int hitsDigits = hitsStr.length();
uint8_t hits_spacing = 8 - hitsDigits;
// Pushing single digit values down right justified.
std::string dbStr = to_string_dec_int(entry.dbValue);
int dbDigits = dbStr.length();
uint8_t db_spacing = 5 - dbDigits;
line += pad_string_with_spaces(hits_spacing) + hitsStr;
line += pad_string_with_spaces(db_spacing) + dbStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
: nav_{nav},
entry_{entry} {
add_children({&button_done,
&button_send,
&button_save,
&label_mac_address,
&text_mac_address,
&label_pdu_type,
&text_pdu_type,
&labels});
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
button_send.on_select = [this, &nav](const ui::Button&) {
auto packetToSend = build_packet();
nav.set_on_pop([packetToSend, &nav]() {
nav.replace<BLETxView>(packetToSend);
});
nav.pop();
};
button_save.on_select = [this, &nav](const ui::Button&) {
auto packetToSave = build_packet();
packetFileBuffer = "";
text_prompt(
nav,
packetFileBuffer,
64,
[this, packetToSave](std::string& buffer) {
on_save_file(buffer, packetToSave);
});
};
}
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);
}
bool BleRecentEntryDetailView::saveFile(const std::filesystem::path& path, BLETxPacket packetToSave) {
File f;
auto error = f.create(path);
if (error)
return false;
std::string macAddressStr = packetToSave.macAddress;
std::string advertisementDataStr = packetToSave.advertisementData;
std::string packetCountStr = packetToSave.packetCount;
std::string packetString = macAddressStr + ' ' + advertisementDataStr + ' ' + packetCountStr;
f.write(packetString.c_str(), packetString.length());
return true;
}
void BleRecentEntryDetailView::update_data() {
}
void BleRecentEntryDetailView::focus() {
button_done.focus();
}
Rect BleRecentEntryDetailView::draw_field(
Painter& painter,
const Rect& draw_rect,
const Style& style,
const std::string& label,
const std::string& value) {
const int label_length_max = 4;
painter.draw_string(Point{draw_rect.left(), draw_rect.top()}, style, label);
painter.draw_string(Point{draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top()}, style, value);
return {draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height()};
}
void BleRecentEntryDetailView::paint(Painter& painter) {
View::paint(painter);
const auto s = style();
const auto rect = screen_rect();
auto field_rect = Rect{rect.left(), rect.top() + 64, rect.width(), 16};
uint8_t type[total_data_lines];
uint8_t length[total_data_lines];
uint8_t data[total_data_lines][40];
int currentByte = 0;
int currentPacket = 0;
int i = 0;
int j = 0;
int k = 0;
switch (entry_.pduType) {
case ADV_IND:
case ADV_NONCONN_IND:
case SCAN_RSP:
case ADV_SCAN_IND: {
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry_.packetData.data;
for (currentByte = 0; (currentByte < entry_.packetData.dataLen) && (currentPacket < total_data_lines);) {
length[currentPacket] = advertiseData->Data[currentByte++];
type[currentPacket] = advertiseData->Data[currentByte++];
// Subtract 1 because type is part of the length.
for (i = 0; i < length[currentPacket] - 1; i++) {
data[currentPacket][i] = advertiseData->Data[currentByte++];
}
currentPacket++;
}
for (i = 0; i < currentPacket; i++) {
uint8_t number_data_lines = ceil((float)(length[i] - 1) / 10.0);
uint8_t current_line = 0;
std::array<std::string, total_data_lines> data_strings{};
for (j = 0; (j < (number_data_lines * 10)) && (j < length[i] - 1); j++) {
if ((j / 10) != current_line) {
current_line++;
}
data_strings[current_line] += to_string_hex(data[i][j], 2);
}
// Read the type back to the total length.
field_rect = draw_field(painter, field_rect, s, to_string_hex(length[i]), to_string_hex(type[i]) + pad_string_with_spaces(3) + data_strings[0]);
if (number_data_lines > 1) {
for (k = 1; k < number_data_lines; k++) {
if (!data_strings[k].empty()) {
field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]);
}
}
}
}
} break;
case ADV_DIRECT_IND:
case SCAN_REQ: {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry_.packetData.data;
uint8_t type = 0xFF;
field_rect = draw_field(painter, field_rect, s, to_string_hex(entry_.packetData.dataLen), to_string_hex(type) + pad_string_with_spaces(3) + to_string_mac_address(directed_mac_data->A1, 6, false));
} break;
case CONNECT_REQ:
default: {
uint8_t type = 0xFF;
// TODO: Display Connect Request Information. For right now just printing full hex data.
// This struct will eventually be used to break apart containing data of Connect Request.
// ADV_PDU_PAYLOAD_TYPE_5 * connect_req = (ADV_PDU_PAYLOAD_TYPE_5 *)entry_.packetData.data;
for (currentByte = 0; (currentByte < entry_.packetData.dataLen); currentByte++) {
data[0][currentByte] = entry_.packetData.data[currentByte];
}
uint8_t number_data_lines = ceil((float)entry_.packetData.dataLen / 10.0);
uint8_t current_line = 0;
std::array<std::string, total_data_lines> data_strings{};
for (j = 0; (j < (number_data_lines * 10)) && (j < entry_.packetData.dataLen); j++) {
if ((j / 10) != current_line) {
current_line++;
}
data_strings[current_line] += to_string_hex(data[0][j], 2);
}
field_rect = draw_field(painter, field_rect, s, to_string_hex(entry_.packetData.dataLen), to_string_hex(type) + pad_string_with_spaces(3) + data_strings[0]);
if (number_data_lines > 1) {
for (k = 1; k < number_data_lines; k++) {
if (!data_strings[k].empty()) {
field_rect = draw_field(painter, field_rect, s, "", pad_string_with_spaces(5) + data_strings[k]);
}
}
}
} break;
}
}
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
entry_ = entry;
set_dirty();
}
BLETxPacket BleRecentEntryDetailView::build_packet() {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
std::string macAddressStr = to_string_mac_address(entry_.packetData.macAddress, 6, true);
strncpy(bleTxPacket.macAddress, macAddressStr.c_str(), 12);
strncpy(bleTxPacket.advertisementData, entry_.dataString.c_str(), entry_.packetData.dataLen * 2);
strncpy(bleTxPacket.packetCount, "50", 3);
bleTxPacket.packet_count = 50;
return bleTxPacket;
}
static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
void BLERxView::focus() {
options_channel.focus();
}
void BLERxView::file_error() {
nav_.display_modal("Error", "File read error.");
}
BLERxView::BLERxView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
&options_channel,
&field_frequency,
&check_log,
&button_find,
&check_name,
&label_sort,
&options_sort,
&label_found,
&text_found_count,
&check_serial_log,
&button_filter,
&button_save_list,
&button_clear_list,
&button_switch,
&recent_entries_view});
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push<BleRecentEntryDetailView>(entry);
};
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
usb_serial_thread = std::make_unique<UsbSerialThread>();
} else {
usb_serial_thread.reset();
}
};
check_serial_log.set_value(serial_logging);
ensure_directory(find_packet_path);
ensure_directory(log_packets_path);
ensure_directory(packet_save_path);
filterBuffer = filter;
button_filter.on_select = [this](Button&) {
text_prompt(
nav_,
filterBuffer,
64,
[this](std::string& buffer) {
on_filter_change(buffer);
});
};
logger = std::make_unique<BLELogger>();
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
logging = v;
if (logger && logging)
logger->append(
"BLERX/Logs"
"/BLELOG_" +
to_string_timestamp(rtc_time::now()) + ".TXT");
};
check_log.set_value(logging);
button_save_list.on_select = [this, &nav](const ui::Button&) {
listFileBuffer = "";
text_prompt(
nav,
listFileBuffer,
64,
[this](std::string& buffer) {
on_save_file(buffer);
});
};
button_clear_list.on_select = [this](Button&) {
recent.clear();
};
button_switch.on_select = [&nav](Button&) {
nav.replace<BLETxView>();
};
field_frequency.set_step(0);
check_name.set_value(name_enable);
check_name.on_select = [this](Checkbox&, bool v) {
name_enable = v;
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
recent_entries_view.set_dirty();
};
options_channel.on_change = [this](size_t index, int32_t v) {
channel_index = (uint8_t)index;
// If we selected Auto don't do anything and Auto will handle changing.
if (v == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(v));
channel_number = v;
baseband::set_btlerx(channel_number);
};
options_sort.on_change = [this](size_t index, int32_t v) {
sort_index = (uint8_t)index;
handle_entries_sort(v);
};
options_channel.set_selected_index(channel_index, true);
options_sort.set_selected_index(sort_index, true);
button_find.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
// nav_.set_on_pop([this]() { button_play.focus(); });
};
};
// Auto-configure modem for LCR RX (will be removed later)
baseband::set_btlerx(channel_number);
receiver_model.enable();
}
std::string BLERxView::build_line_str(BleRecentEntry entry) {
std::string macAddressStr = to_string_mac_address(entry.packetData.macAddress, 6, false) + ",";
std::string timestameStr = entry.timestamp + ",";
std::string nameStr = entry.nameString + ",";
std::string pduStr = pdu_type_to_string(entry.pduType) + ",";
std::string dataStr = "0x" + entry.dataString + ",";
std::string hitsStr = to_string_dec_int(entry.numHits) + ",";
std::string dbStr = to_string_dec_int(entry.dbValue) + ",";
std::string channelStr = to_string_dec_int(entry.channelNumber) + ",";
std::string lineStr = timestameStr + macAddressStr + nameStr + pduStr + dataStr + hitsStr + dbStr + channelStr;
lineStr += pad_string_with_spaces(maxLineLength - lineStr.length());
return lineStr;
}
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);
}
bool BLERxView::saveFile(const std::filesystem::path& path) {
// Check to see if file was previously saved.
bool file_existed = file_exists(path);
// Attempt to open, if it can't be opened. Create new.
auto src = std::make_unique<File>();
auto error = src->open(path, false, true);
if (error) {
return false;
}
for (const auto& entry : recent) {
tempList.emplace_back(entry);
}
if (!file_existed) {
src->write_line(headerStr.c_str());
auto it = tempList.begin();
while (it != tempList.end()) {
BleRecentEntry entry = (BleRecentEntry)*it;
src->write_line(build_line_str(entry).c_str());
it++;
}
} else {
// Check file for macAddressStr before adding.
char currentLine[maxLineLength];
uint64_t startPos = headerStr.length();
uint64_t bytesRead = 0;
uint64_t bytePos = 0;
File::Size currentSize = src->size();
auto dst = std::make_unique<File>();
const std::filesystem::path tempFilePath = path + "~";
auto error = dst->open(tempFilePath, false, true);
if (error) {
return false;
}
dst->write_line(headerStr.c_str());
src->seek(startPos);
// Look for ones found and rewrite.
do {
memset(currentLine, 0, maxLineLength);
bytesRead = readUntil(*src, currentLine, currentSize, '\n');
if (!bytesRead) {
break;
}
bytePos += bytesRead;
std::string lineStr = "";
std::string macAddressStr = "";
BleRecentEntry foundEntry;
char* token;
token = strtok(currentLine, ",");
while (token != NULL) {
auto it = tempList.begin();
while (it != tempList.end()) {
BleRecentEntry& entry = reinterpret_cast<BleRecentEntry&>(*it);
macAddressStr = to_string_mac_address(entry.packetData.macAddress, 6, false);
if (strstr(token, macAddressStr.c_str()) != NULL) {
entry.entryFound = true;
foundEntry = entry;
break;
}
it++;
}
if (foundEntry.entryFound) {
break;
}
token = strtok(NULL, ",");
}
if (foundEntry.entryFound) {
dst->write_line(build_line_str(foundEntry).c_str());
}
} while (bytePos <= currentSize);
// Write the ones not found.
auto it = tempList.begin();
while (it != tempList.end()) {
BleRecentEntry entry = (BleRecentEntry)*it;
if (!entry.entryFound) {
dst->write_line(build_line_str(entry).c_str());
}
it++;
}
// Close files before renaming/deleting.
src.reset();
dst.reset();
// Delete original and overwrite with temp file.
delete_file(path);
rename_file(tempFilePath, path);
}
tempList.clear();
return true;
}
void BLERxView::on_data(BlePacketData* packet) {
if (!logging) {
str_log = "";
}
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
str_console += " Mac:";
str_console += to_string_mac_address(packet->macAddress, 6, false);
str_console += " Data:";
int i;
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
}
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
// Start of Packet stuffing.
// Masking off the top 2 bytes to avoid invalid keys.
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
// Add entries if they meet the criteria.
auto value = filter;
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
});
handle_entries_sort(options_sort.selected_index());
// Log at End of Packet.
if (logger && logging) {
logger->log_raw_data(str_console + "\r\n");
}
if (serial_logging) {
usb_serial_thread->serial_str = str_console + "\r\n";
usb_serial_thread->str_ready = true;
}
str_console = "";
if (!searchList.empty()) {
auto it = searchList.begin();
while (it != searchList.end()) {
std::string searchStr = (std::string)*it;
if (entry.dataString.find(searchStr) != std::string::npos) {
searchList.erase(it);
found_count++;
break;
}
it++;
}
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
}
}
void BLERxView::on_filter_change(std::string value) {
// New filter? Reset list from recent entries.
if (filter != value) {
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
});
}
filter = value;
}
void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) {
file_path = fs::path(u"/") + new_file_path;
found_count = 0;
total_count = 0;
searchList.clear();
{ // Get the size of the data file.
File data_file;
auto error = data_file.open(file_path, true, false);
if (error) {
file_error();
file_path = "";
return;
}
uint64_t bytesRead = 0;
uint64_t bytePos = 0;
char currentLine[maxLineLength];
do {
memset(currentLine, 0, maxLineLength);
bytesRead = readUntil(data_file, currentLine, maxLineLength, '\n');
// Remove return if found.
if (currentLine[strlen(currentLine)] == '\r') {
currentLine[strlen(currentLine)] = '\0';
}
if (!bytesRead) {
break;
}
searchList.push_back(currentLine);
total_count++;
bytePos += bytesRead;
} while (bytePos <= data_file.size());
}
}
// called each 1/60th of second, so 6 = 100ms
void BLERxView::on_timer() {
if (++timer_count == timer_period) {
timer_count = 0;
if (auto_channel) {
int min = 37;
int max = 39;
int randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
baseband::set_btlerx(randomChannel);
}
}
}
void BLERxView::handle_entries_sort(uint8_t index) {
switch (index) {
case 0:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
break;
case 1:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.numHits; }, false);
break;
case 2:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, false);
break;
case 3:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
break;
case 4:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.nameString; }, true);
break;
default:
break;
}
recent_entries_view.set_dirty();
}
void BLERxView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height - switch_button_height};
recent_entries_view.set_parent_rect(content_rect);
}
BLERxView::~BLERxView() {
receiver_model.disable();
baseband::shutdown();
}
void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
std::string data_string;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB;
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
entry.packetData.type = packet->type;
entry.packetData.size = packet->size;
entry.packetData.dataLen = packet->dataLen;
// Mac Address of sender.
entry.packetData.macAddress[0] = packet->macAddress[0];
entry.packetData.macAddress[1] = packet->macAddress[1];
entry.packetData.macAddress[2] = packet->macAddress[2];
entry.packetData.macAddress[3] = packet->macAddress[3];
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
entry.numHits++;
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
entry.packetData.data[i] = packet->data[i];
}
entry.include_name = check_name.value();
// Only parse name for advertisment packets and empty name entries
if ((pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) && entry.nameString.empty()) {
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
uint8_t currentByte = 0;
uint8_t length = 0;
uint8_t type = 0;
std::string decoded_data;
for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
length = advertiseData->Data[currentByte++];
type = advertiseData->Data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; i < length - 1; i++) {
if (type == 0x08 || type == 0x09) {
decoded_data += (char)advertiseData->Data[currentByte];
}
currentByte++;
}
if (!decoded_data.empty()) {
entry.nameString = std::move(decoded_data);
break;
}
}
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
reverse_byte_array(directed_mac_data->A1, 6);
}
}
} /* namespace ui */
+377
View File
@@ -0,0 +1,377 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 __BLE_RX_APP_H__
#define __BLE_RX_APP_H__
#include "ble_tx_app.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "usb_serial_thread.hpp"
#include "recent_entries.hpp"
class BLELogger {
public:
Optional<File::Error> append(const std::string& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data);
private:
LogFile log_file{};
};
namespace ui {
typedef enum {
ADV_IND = 0,
ADV_DIRECT_IND = 1,
ADV_NONCONN_IND = 2,
SCAN_REQ = 3,
SCAN_RSP = 4,
CONNECT_REQ = 5,
ADV_SCAN_IND = 6,
RESERVED0 = 7,
RESERVED1 = 8,
RESERVED2 = 9,
RESERVED3 = 10,
RESERVED4 = 11,
RESERVED5 = 12,
RESERVED6 = 13,
RESERVED7 = 14,
RESERVED8 = 15
} ADV_PDU_TYPE;
struct BleRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0xffffffff;
uint64_t macAddress;
int dbValue;
BlePacketData packetData;
std::string timestamp;
std::string dataString;
std::string nameString;
bool include_name;
uint16_t numHits;
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
bool entryFound;
BleRecentEntry()
: BleRecentEntry{0} {
}
BleRecentEntry(
const uint64_t macAddress)
: macAddress{macAddress},
dbValue{},
packetData{},
timestamp{},
dataString{},
nameString{},
include_name{},
numHits{},
pduType{},
channelNumber{},
entryFound{} {
}
Key key() const {
return macAddress;
}
};
using BleRecentEntries = RecentEntries<BleRecentEntry>;
using BleRecentEntriesView = RecentEntriesView<BleRecentEntries>;
class BleRecentEntryDetailView : public View {
public:
BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry);
void set_entry(const BleRecentEntry& new_entry);
const BleRecentEntry& entry() const { return entry_; };
void update_data();
void focus() override;
void paint(Painter&) override;
private:
NavigationView& nav_;
BleRecentEntry entry_{};
BLETxPacket build_packet();
void on_save_file(const std::string value, BLETxPacket packetToSave);
bool saveFile(const std::filesystem::path& path, BLETxPacket packetToSave);
std::string packetFileBuffer{};
std::filesystem::path packet_save_path{u"BLERX/Lists/????.csv"};
static constexpr uint8_t total_data_lines{5};
Labels label_mac_address{
{{0 * 8, 0 * 16}, "Mac Address:", Color::light_grey()}};
Text text_mac_address{
{12 * 8, 0 * 16, 17 * 8, 16},
"-"};
Labels label_pdu_type{
{{0 * 8, 1 * 16}, "PDU Type:", Color::light_grey()}};
Text text_pdu_type{
{9 * 8, 1 * 16, 17 * 8, 16},
"-"};
Labels labels{
{{0 * 8, 3 * 16}, "Len", Color::light_grey()},
{{5 * 8, 3 * 16}, "Type", Color::light_grey()},
{{10 * 8, 3 * 16}, "Value", Color::light_grey()},
};
Button button_send{
{19, 224, 96, 24},
"Send"};
Button button_done{
{125, 224, 96, 24},
"Done"};
Button button_save{
{72, 264, 96, 24},
"Save"};
bool send_updates{false};
Rect draw_field(
Painter& painter,
const Rect& draw_rect,
const Style& style,
const std::string& label,
const std::string& value);
};
class BLERxView : public View {
public:
BLERxView(NavigationView& nav);
~BLERxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void focus() override;
std::string title() const override { return "BLE RX"; };
private:
std::string build_line_str(BleRecentEntry entry);
void on_save_file(const std::string value);
bool saveFile(const std::filesystem::path& path);
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
void on_data(BlePacketData* packetData);
void on_filter_change(std::string value);
void on_file_changed(const std::filesystem::path& new_file_path);
void file_error();
void on_timer();
void handle_entries_sort(uint8_t index);
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
NavigationView& nav_;
RxRadioState radio_state_{
2402000000 /* frequency */,
4000000 /* bandwidth */,
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
uint8_t channel_index{0};
uint8_t sort_index{0};
std::string filter{};
bool logging{false};
bool serial_logging{false};
bool name_enable{true};
app_settings::SettingsManager settings_{
"rx_ble",
app_settings::Mode::RX,
{
{"channel_index"sv, &channel_index},
{"sort_index"sv, &sort_index},
{"filter"sv, &filter},
{"log"sv, &logging},
// disabled to always start without USB serial activated until we can make it non blocking if not connected
// {"serial_log"sv, &serial_logging},
{"name"sv, &name_enable},
}};
std::string str_console = "";
uint8_t console_color{0};
uint32_t prev_value{0};
uint8_t channel_number = 37;
bool auto_channel = false;
int16_t timer_count{0};
int16_t timer_period{6}; // 100ms
std::string filterBuffer{};
std::string listFileBuffer{};
std::string headerStr = "Timestamp, MAC Address, Name, Packet Type, Data, Hits, dB, Channel";
uint16_t maxLineLength = 140;
std::filesystem::path file_path{};
uint64_t found_count = 0;
uint64_t total_count = 0;
std::vector<std::string> searchList{};
std::filesystem::path find_packet_path{u"BLERX/Find/????.TXT"};
std::filesystem::path log_packets_path{u"BLERX/Logs/????.TXT"};
std::filesystem::path packet_save_path{u"BLERX/Lists/????.csv"};
static constexpr auto header_height = 4 * 16;
static constexpr auto switch_button_height = 3 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
Labels label_sort{
{{0 * 8, 3 * 8}, "Sort:", Color::light_grey()}};
OptionsField options_sort{
{5 * 8, 3 * 8},
4,
{{"MAC", 0},
{"Hits", 1},
{"dB", 2},
{"Time", 3},
{"Name", 4}}};
Button button_filter{
{11 * 8, 3 * 8, 4 * 8, 16},
"Filter"};
Checkbox check_log{
{17 * 8, 3 * 8},
3,
"Log",
true};
Checkbox check_name{
{23 * 8, 3 * 8},
3,
"Name",
true};
Button button_find{
{0 * 8, 6 * 8, 4 * 8, 16},
"Find"};
Labels label_found{
{{5 * 8, 6 * 8}, "Found:", Color::light_grey()}};
Text text_found_count{
{11 * 8, 3 * 16, 20 * 8, 16},
"0/0"};
Checkbox check_serial_log{
{17 * 8, 3 * 16 - 2},
7,
"USB Log",
true};
Console console{
{0, 4 * 16, 240, 240}};
Button button_clear_list{
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
{11 * 8, 320 - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
{240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
std::unique_ptr<BLELogger> logger{};
BleRecentEntries recent{};
BleRecentEntries tempList{};
const RecentEntriesColumns columns{{
{"Mac Address", 17},
{"Hits", 7},
{"dB", 4},
}};
BleRecentEntriesView recent_entries_view{columns, recent};
MessageHandlerRegistration message_handler_packet{
Message::ID::BlePacket,
[this](Message* const p) {
const auto message = static_cast<const BLEPacketMessage*>(p);
this->on_data(message->packet);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
};
} /* namespace ui */
#endif /*__UI_AFSK_RX_H__*/
+561
View File
@@ -0,0 +1,561 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 "ble_tx_app.hpp"
#include "ble_rx_app.hpp"
#include "ui_fileman.hpp"
#include "ui_modemsetup.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "io_file.hpp"
#include "modems.hpp"
#include "portapack_persistent_memory.hpp"
#include "rtc_time.hpp"
#include "string_format.hpp"
using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
void BLELoggerTx::log_raw_data(const std::string& data) {
log_file.write_entry(data);
}
bool hasValidHexPairs(const std::string& str, int totalPairs) {
int validPairs = 0;
for (int i = 0; i < totalPairs * 2; i += 2) {
char c1 = str[i];
char c2 = str[i + 1];
if (!(std::isxdigit(c1) && std::isxdigit(c2))) {
return false; // Return false if any pair is invalid.
}
validPairs++;
}
return (validPairs == totalPairs);
}
std::vector<std::string> splitIntoStrings(const char* input) {
std::vector<std::string> result;
int length = std::strlen(input);
int start = 0;
while (start < length) {
int remaining = length - start;
int chunkSize = (remaining > 29) ? 29 : remaining;
result.push_back(std::string(input + start, chunkSize));
start += chunkSize;
}
return result;
}
uint32_t stringToUint32(const std::string& str) {
size_t pos = 0;
uint32_t result = 0;
while (pos < str.size() && std::isdigit(str[pos])) {
int digit = str[pos] - '0';
// Check for overflow before adding the next digit
if (result > (UINT32_MAX - digit) / 10) {
return 0;
}
result = result * 10 + digit;
pos++;
}
return result;
}
static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
uint64_t freq_hz;
switch (channel_number) {
case 37:
freq_hz = 2'402'000'000ull;
break;
case 38:
freq_hz = 2'426'000'000ull;
break;
case 39:
freq_hz = 2'480'000'000ull;
break;
case 0 ... 10:
freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull;
break;
case 11 ... 36:
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
}
return freq_hz;
}
namespace ui {
void BLETxView::focus() {
button_open.focus();
}
bool BLETxView::is_active() const {
return (bool)is_running;
}
void BLETxView::file_error() {
nav_.display_modal("Error", "File read error.");
}
bool BLETxView::saveFile(const std::filesystem::path& path) {
File f;
auto error = f.create(path);
if (error)
return false;
for (uint32_t i = 0; i < num_packets; i++) {
std::string macAddressStr = packets[i].macAddress;
std::string advertisementDataStr = packets[i].advertisementData;
std::string packetCountStr = packets[i].packetCount;
std::string packetString = macAddressStr + ' ' + advertisementDataStr + ' ' + packetCountStr;
// Are we on the last line?
if (i != num_packets - 1) {
packetString += '\n';
}
f.write(packetString.c_str(), packetString.length());
}
return true;
}
void BLETxView::toggle() {
if (is_active()) {
stop();
} else {
start();
}
}
void BLETxView::start() {
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
// Generate new random Mac Address.
generateRandomMacAddress(randomMac);
// If this is our first run, check file.
if (!is_active()) {
File data_file;
auto error = data_file.open(file_path);
if (error && !file_override) {
file_error();
check_loop.set_value(false);
return;
}
button_play.set_bitmap(&bitmap_stop);
is_running = true;
}
char advertisementData[63] = {0};
strcpy(advertisementData, packets[current_packet].advertisementData);
// TODO: Make this a checkbox.
if (!markedBytes.empty()) {
for (size_t i = 0; i < strlen(advertisementData); i++) {
bool found = false;
auto it = std::find(markedBytes.begin(), markedBytes.end(), i);
if (it != markedBytes.end()) {
found = true;
}
if (found) {
uint8_t hexDigit;
switch (marked_data_sequence.selected_index_value()) {
case 0:
hexDigit = marked_counter++;
break;
case 1:
hexDigit = marked_counter--;
break;
case 2: {
uint8_t min = 0x00;
uint8_t max = 0x0F;
hexDigit = min + std::rand() % (max - min + 1);
} break;
default:
hexDigit = 0;
break;
}
advertisementData[i] = uint_to_char(hexDigit, 16);
// Bounding to Hex.
if (marked_counter == 16) {
marked_counter = 0;
} else if (marked_counter == 255) {
marked_counter = 15;
}
}
}
}
// Setup next packet configuration.
progressbar.set_max(packets[current_packet].packet_count);
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, pduType);
}
void BLETxView::stop() {
transmitter_model.disable();
baseband::shutdown();
progressbar.set_value(0);
button_play.set_bitmap(&bitmap_play);
check_loop.set_value(false);
update_current_packet(packets[0], 0);
is_running = false;
}
void BLETxView::reset() {
transmitter_model.disable();
baseband::shutdown();
start();
}
// called each 1/60th of second, so 6 = 100ms
void BLETxView::on_timer() {
if (++timer_count == timer_period) {
timer_count = 0;
if (is_active()) {
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
if (current_packet == (num_packets - 1)) {
current_packet = 0;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
reset();
} else {
stop();
}
} else {
current_packet++;
update_current_packet(packets[current_packet], current_packet);
reset();
}
} else {
reset();
}
}
}
if (++auto_channel_counter == auto_channel_period) {
auto_channel_counter = 0;
if (auto_channel) {
int min = 37;
int max = 39;
channel_number = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(channel_number));
}
}
}
void BLETxView::on_tx_progress(const bool done) {
if (done) {
if (is_active()) {
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
packet_counter--;
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
}
}
}
BLETxView::BLETxView(NavigationView& nav)
: nav_{nav} {
add_children({&button_open,
&text_filename,
&progressbar,
&check_rand_mac,
&field_frequency,
&tx_view, // now it handles previous rfgain, rfamp.
&check_loop,
&button_play,
&label_speed,
&options_speed,
&options_channel,
&options_adv_type,
&label_marked_data,
&marked_data_sequence,
&label_packet_index,
&text_packet_index,
&label_packets_sent,
&text_packets_sent,
&label_mac_address,
&text_mac_address,
&label_data_packet,
&dataEditView,
&button_clear_marked,
&button_save_packet,
&button_switch});
field_frequency.set_step(0);
ensure_directory(packet_save_path);
button_play.on_select = [this](ImageButton&) {
this->toggle();
};
options_channel.on_change = [this](size_t, int32_t i) {
// If we selected Auto don't do anything and Auto will handle changing.
if (i == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number = i;
};
options_speed.on_change = [this](size_t, int32_t i) {
timer_period = i;
timer_count = 0;
};
options_adv_type.on_change = [this](size_t, int32_t i) {
pduType = (PKT_TYPE)i;
};
options_speed.set_selected_index(0);
options_channel.set_selected_index(0);
options_adv_type.set_selected_index(0);
check_rand_mac.set_value(false);
check_rand_mac.on_select = [this](Checkbox&, bool v) {
random_mac = v;
};
button_open.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
nav_.set_on_pop([this]() { button_play.focus(); });
};
};
button_save_packet.on_select = [this, &nav](Button&) {
packetFileBuffer = "";
text_prompt(
nav,
packetFileBuffer,
64,
[this](std::string& buffer) {
on_save_file(buffer);
});
};
button_switch.on_select = [&nav](Button&) {
nav.replace<BLERxView>();
};
dataEditView.on_select = [this] {
// Save last selected cursor.
cursor_pos.line = dataEditView.line();
cursor_pos.col = dataEditView.col();
// Reject setting newline at index 29.
if (cursor_pos.col != 29) {
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
auto it = std::find(markedBytes.begin(), markedBytes.end(), dataBytePos);
if (it != markedBytes.end()) {
markedBytes.erase(it);
} else {
markedBytes.push_back(dataBytePos);
}
dataEditView.cursor_mark_selected();
}
};
button_clear_marked.on_select = [this](Button&) {
marked_counter = 0;
markedBytes.clear();
dataEditView.cursor_clear_marked();
};
}
BLETxView::BLETxView(
NavigationView& nav,
BLETxPacket packet)
: BLETxView(nav) {
packets[0] = packet;
update_current_packet(packets[0], 0);
num_packets = 1;
file_override = true;
}
void BLETxView::on_file_changed(const fs::path& new_file_path) {
file_path = fs::path(u"/") + new_file_path;
num_packets = 0;
{ // Get the size of the data file.
File data_file;
auto error = data_file.open(file_path);
if (error) {
file_error();
file_path = "";
return;
}
do {
readUntil(data_file, packets[num_packets].macAddress, mac_address_size_str, ' ');
readUntil(data_file, packets[num_packets].advertisementData, max_packet_size_str, ' ');
readUntil(data_file, packets[num_packets].packetCount, max_packet_repeat_str, '\n');
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
uint64_t packetCountSize = strlen(packets[num_packets].packetCount);
packets[num_packets].packet_count = stringToUint32(packets[num_packets].packetCount);
// Verify Data.
if ((macAddressSize == mac_address_size_str) && (advertisementDataSize < max_packet_size_str) && (packetCountSize < max_packet_repeat_str) &&
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) && hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) && (packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count)) {
text_filename.set(truncate(file_path.filename().string(), 12));
} else {
// Did not find any packets.
if (num_packets == 0) {
file_path = "";
return;
}
break;
}
num_packets++;
} while (num_packets < max_num_packets);
update_current_packet(packets[0], 0);
}
}
void BLETxView::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);
}
void BLETxView::on_data(uint32_t value, bool is_data) {
std::string str_console = "";
if (is_data) {
str_console += (char)(value);
}
}
void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex) {
std::string formattedMacAddress = to_string_formatted_mac_address(packet.macAddress);
std::vector<std::string> strings = splitIntoStrings(packet.advertisementData);
text_packet_index.set(to_string_dec_uint(current_packet));
text_packets_sent.set(to_string_dec_uint(packet.packet_count));
text_mac_address.set(formattedMacAddress);
packet_counter = packet.packet_count;
current_packet = currentIndex;
dataFile.create(dataTempFilePath);
for (const std::string& str : strings) {
dataFile.write(str.c_str(), str.size());
dataFile.write("\n", 1);
;
}
dataFile.~File();
auto result = FileWrapper::open(dataTempFilePath);
if (!result)
return;
dataFileWrapper = *std::move(result);
dataEditView.set_font_zoom(true);
dataEditView.set_file(*dataFileWrapper);
dataEditView.redraw(true, true);
}
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height - switch_button_height};
dataEditView.set_parent_rect(content_rect);
}
BLETxView::~BLETxView() {
delete_file(dataTempFilePath);
transmitter_model.disable();
baseband::shutdown();
}
} /* namespace ui */
+337
View File
@@ -0,0 +1,337 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
*
* 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 __BLE_TX_APP_H__
#define __BLE_TX_APP_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_transmitter.hpp"
#include "ui_text_editor.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "replay_thread.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "recent_entries.hpp"
#include <string>
#include <memory>
class BLELoggerTx {
public:
Optional<File::Error> append(const std::string& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data);
private:
LogFile log_file{};
};
namespace ui {
enum PKT_TYPE {
PKT_TYPE_INVALID_TYPE,
PKT_TYPE_RAW,
PKT_TYPE_DISCOVERY,
PKT_TYPE_IBEACON,
PKT_TYPE_ADV_IND,
PKT_TYPE_ADV_DIRECT_IND,
PKT_TYPE_ADV_NONCONN_IND,
PKT_TYPE_ADV_SCAN_IND,
PKT_TYPE_SCAN_REQ,
PKT_TYPE_SCAN_RSP,
PKT_TYPE_CONNECT_REQ,
PKT_TYPE_LL_DATA,
PKT_TYPE_LL_CONNECTION_UPDATE_REQ,
PKT_TYPE_LL_CHANNEL_MAP_REQ,
PKT_TYPE_LL_TERMINATE_IND,
PKT_TYPE_LL_ENC_REQ,
PKT_TYPE_LL_ENC_RSP,
PKT_TYPE_LL_START_ENC_REQ,
PKT_TYPE_LL_START_ENC_RSP,
PKT_TYPE_LL_UNKNOWN_RSP,
PKT_TYPE_LL_FEATURE_REQ,
PKT_TYPE_LL_FEATURE_RSP,
PKT_TYPE_LL_PAUSE_ENC_REQ,
PKT_TYPE_LL_PAUSE_ENC_RSP,
PKT_TYPE_LL_VERSION_IND,
PKT_TYPE_LL_REJECT_IND,
PKT_TYPE_NUM_PKT_TYPE
};
struct BLETxPacket {
char macAddress[13];
char advertisementData[63];
char packetCount[11];
uint32_t packet_count;
PKT_TYPE packetType;
};
class BLETxView : public View {
public:
BLETxView(NavigationView& nav);
BLETxView(NavigationView& nav, BLETxPacket packet);
~BLETxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void focus() override;
bool is_active() const;
void toggle();
void start();
void stop();
void reset();
void handle_replay_thread_done(const uint32_t return_code);
void file_error();
bool saveFile(const std::filesystem::path& path);
std::string title() const override { return "BLE TX"; };
private:
void on_timer();
void on_data(uint32_t value, bool is_data);
void on_file_changed(const std::filesystem::path& new_file_path);
void on_save_file(const std::string value);
void on_tx_progress(const bool done);
void on_random_data_change(std::string value);
void update_current_packet(BLETxPacket packet, uint32_t currentIndex);
NavigationView& nav_;
TxRadioState radio_state_{
2'402'000'000 /* frequency */,
4'000'000 /* bandwidth */,
4'000'000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_ble", app_settings::Mode::TX};
uint8_t console_color{0};
uint32_t prev_value{0};
std::filesystem::path file_path{};
std::filesystem::path packet_save_path{u"BLETX/BLETX_????.TXT"};
uint8_t channel_number = 37;
bool auto_channel = false;
char randomMac[13] = "010203040506";
bool is_running = false;
int16_t timer_count{0};
int16_t timer_period{1};
int16_t auto_channel_counter = 0;
int16_t auto_channel_period{6};
bool repeatLoop = false;
uint32_t packet_counter{0};
uint32_t num_packets{0};
uint32_t current_packet{0};
bool random_mac = false;
bool file_override = false;
typedef struct {
uint16_t line;
uint16_t col;
} CursorPos;
std::unique_ptr<FileWrapper> dataFileWrapper{};
File dataFile{};
std::filesystem::path dataTempFilePath{u"BLETX/dataFileTemp.TXT"};
std::vector<uint16_t> markedBytes{};
CursorPos cursor_pos{};
uint8_t marked_counter = 0;
static constexpr uint8_t mac_address_size_str{12};
static constexpr uint8_t max_packet_size_str{62};
static constexpr uint8_t max_packet_repeat_str{10};
static constexpr uint32_t max_packet_repeat_count{UINT32_MAX};
static constexpr uint32_t max_num_packets{32};
BLETxPacket packets[max_num_packets];
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
static constexpr auto header_height = 10 * 16;
static constexpr auto switch_button_height = 6 * 16;
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
"Open file"};
Text text_filename{
{11 * 8, 0 * 16, 12 * 8, 16},
"-"};
ProgressBar progressbar{
{11 * 8, 1 * 16, 9 * 8, 16}};
Checkbox check_rand_mac{
{21 * 8, 1 * 16},
6,
"?? Mac",
true};
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
nav_};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
4,
"Loop",
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Color::green(),
Color::black()};
Labels label_speed{
{{0 * 8, 6 * 8}, "Speed:", Color::light_grey()}};
OptionsField options_speed{
{7 * 8, 6 * 8},
3,
{{"1 ", 1}, // 16ms
{"2 ", 2}, // 32ms
{"3 ", 3}, // 48ms
{"4 ", 6}, // 100ms
{"5 ", 12}}}; // 200ms
OptionsField options_channel{
{11 * 8, 6 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
OptionsField options_adv_type{
{17 * 8, 6 * 8},
14,
{{"DISCOVERY ", PKT_TYPE_DISCOVERY},
{"ADV_IND", PKT_TYPE_ADV_IND},
{"ADV_DIRECT", PKT_TYPE_ADV_DIRECT_IND},
{"ADV_NONCONN", PKT_TYPE_ADV_NONCONN_IND},
{"ADV_SCAN_IND", PKT_TYPE_ADV_SCAN_IND},
{"SCAN_REQ", PKT_TYPE_SCAN_REQ},
{"SCAN_RSP", PKT_TYPE_SCAN_RSP},
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
Labels label_marked_data{
{{0 * 8, 4 * 16}, "Marked Data:", Color::light_grey()}};
OptionsField marked_data_sequence{
{12 * 8, 8 * 8},
8,
{{"Ascend", 0},
{"Descend", 1},
{"Random", 2}}};
Labels label_packet_index{
{{0 * 8, 12 * 8}, "Packet Index:", Color::light_grey()}};
Text text_packet_index{
{13 * 8, 6 * 16, 12 * 8, 16},
"-"};
Labels label_packets_sent{
{{0 * 8, 14 * 8}, "Repeat Count:", Color::light_grey()}};
Text text_packets_sent{
{13 * 8, 7 * 16, 12 * 8, 16},
"-"};
Labels label_mac_address{
{{0 * 8, 16 * 8}, "Mac Address:", Color::light_grey()}};
Text text_mac_address{
{12 * 8, 8 * 16, 20 * 8, 16},
"-"};
Labels label_data_packet{
{{0 * 8, 9 * 16}, "Packet Data:", Color::light_grey()}};
Console console{
{0, 9 * 18, 240, 240}};
TextViewer dataEditView{
{0, 9 * 18, 240, 240}};
Button button_clear_marked{
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
"Clear Marked"};
Button button_save_packet{
{1 * 8, 16 * 16, 13 * 8, 2 * 16},
"Save Packet"};
Button button_switch{
{16 * 8, 16 * 16, 13 * 8, 2 * 16},
"Switch to Rx"};
std::string str_log{""};
bool logging{true};
bool logging_done{false};
std::string packetFileBuffer{};
std::unique_ptr<BLELoggerTx> logger{};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(p);
this->on_data(message->value, message->is_data);
}};
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_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
};
} /* namespace ui */
#endif /*__UI_AFSK_RX_H__*/
+46 -92
View File
@@ -21,15 +21,16 @@
*/
#include "capture_app.hpp"
#include "baseband_api.hpp"
#include "portapack.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
namespace ui {
CaptureAppView::CaptureAppView(NavigationView& nav) {
CaptureAppView::CaptureAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_capture);
add_children({
@@ -42,90 +43,61 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
&field_lna,
&field_vga,
&option_bandwidth,
&option_format,
&check_trim,
&record_view,
&waterfall,
});
// Hack for initialization
// TODO: This should be included in a more global section so apps dont need to do it
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
//-------------------
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_tuning_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
field_frequency_step.set_by_value(receiver_model.frequency_step());
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
receiver_model.set_frequency_step(v);
this->field_frequency.set_step(v);
};
option_bandwidth.on_change = [this](size_t, uint32_t base_rate) {
sampling_rate = 8 * base_rate; // Decimation by 8 done on baseband side
/* base_rate 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 */
waterfall.on_hide();
record_view.set_sampling_rate(sampling_rate);
receiver_model.set_sampling_rate(sampling_rate);
/* Set up proper anti aliasing BPF bandwith in MAX2837 before ADC sampling according to the new added BW Options . */
switch (sampling_rate) { // we use the var fs (sampling_rate) , to set up BPF aprox < fs_max/2 by Nyquist theorem.
case 0 ... 2000000: // BW Captured range (0 <= 250kHz max ) fs = 8 x 250 kHz
anti_alias_baseband_bandwidth_filter = 1750000; // Minimum BPF MAX2837 for all those lower BW options.
break;
case 4000000 ... 6000000: // BW capture range (500k ... 750kHz max ) fs_max = 8 x 750kHz = 6Mhz
// BW 500k ... 750kHz , ex. 500kHz (fs = 8*BW = 4Mhz) , BW 600kHz (fs = 4,8Mhz) , BW 750 kHz (fs = 6Mhz)
anti_alias_baseband_bandwidth_filter = 2500000; // in some IC MAX2837 appear 2250000 , but both works similar.
break;
case 8800000: // BW capture 1,1Mhz fs = 8 x 1,1Mhz = 8,8Mhz . (1Mhz showed slightly higher noise background).
anti_alias_baseband_bandwidth_filter = 3500000;
break;
case 14000000: // BW capture 1,75Mhz , fs = 8 x 1,75Mhz = 14Mhz
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
anti_alias_baseband_bandwidth_filter = 5000000;
break;
case 16000000: // BW capture 2Mhz , fs = 8 x 2Mhz = 16Mhz
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
anti_alias_baseband_bandwidth_filter = 6000000;
break;
case 20000000: // BW capture 2,5Mhz , fs= 8 x 2,5 Mhz = 20Mhz
// good BPF ,good matching, but LCD making flicker , refresh rate should be < 20 Hz , but reasonable picture
anti_alias_baseband_bandwidth_filter = 7000000;
break;
default: // BW capture 2,75Mhz, fs = 8 x 2,75Mhz= 22Mhz max ADC sampling) and others.
// We tested also 9Mhz FPB stightly too much noise floor , better 8Mhz
anti_alias_baseband_bandwidth_filter = 8000000;
}
receiver_model.set_baseband_bandwidth(anti_alias_baseband_bandwidth_filter);
waterfall.on_show();
option_format.set_selected_index(0); // Default to C16
option_format.on_change = [this](size_t, uint32_t file_type) {
record_view.set_file_type((RecordView::FileType)file_type);
};
option_bandwidth.set_selected_index(7); // 500k, Preselected starting default option 500kHz
check_trim.on_select = [this](Checkbox&, bool v) {
record_view.set_auto_trim(v);
};
freqman_set_bandwidth_option(SPEC_MODULATION, option_bandwidth);
option_bandwidth.on_change = [this](size_t, uint32_t bandwidth) {
/* Nyquist would imply a sample rate of 2x bandwidth, but because the ADC
* 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. */
/* 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. */
waterfall.stop();
// 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);
// Update the radio model with the actual sampling rate.
receiver_model.set_sampling_rate(actual_sample_rate);
// Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
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);
}
previous_bandwidth = bandwidth;
waterfall.start();
};
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
receiver_model.enable();
option_bandwidth.set_by_value(500000);
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
@@ -133,28 +105,14 @@ CaptureAppView::CaptureAppView(NavigationView& nav) {
}
CaptureAppView::~CaptureAppView() {
// Hack for preventing halting other apps
// TODO: This should be also part of something global
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// ----------------------------
receiver_model.disable();
baseband::shutdown();
}
void CaptureAppView::on_hide() {
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
waterfall.on_hide();
View::on_hide();
}
void CaptureAppView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
@@ -162,8 +120,4 @@ void CaptureAppView::focus() {
record_view.focus();
}
void CaptureAppView::on_tuning_frequency_changed(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
} /* namespace ui */
+30 -35
View File
@@ -26,25 +26,11 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "ui_spectrum.hpp"
#define BW_OPTIONS \
{" 8k5", 8500}, \
{" 11k", 11000}, \
{" 16k", 16000}, \
{" 25k", 25000}, \
{" 50k", 50000}, \
{" 100k", 100000}, \
{" 250k", 250000}, \
{" 500k", 500000}, /* Previous Limit bandwith Option with perfect micro SD write .C16 format operaton.*/ \
{" 600k", 600000}, /* That extended option is still possible to record with FW version Mayhem v1.41 (< 2,5MB/sec) */ \
{" 750k", 750000}, /* From that BW onwards, the LCD is ok, but the recorded file is auto decimated,(not real file size) */ \
{"1100k", 1100000}, \
{"1750k", 1750000}, \
{"2000k", 2000000}, \
{"2500k", 2500000}, \
{"2750k", 2750000}, // That is our max Capture option , to keep using later / 8 decimation (22Mhz sampling ADC)
#include "app_settings.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -53,41 +39,38 @@ class CaptureAppView : public View {
CaptureAppView(NavigationView& nav);
~CaptureAppView();
void on_hide() override;
void set_parent_rect(const Rect new_parent_rect) override;
void focus() override;
void set_parent_rect(const Rect new_parent_rect) override;
std::string title() const override { return "Capture"; };
private:
static constexpr ui::Dim header_height = 3 * 16;
uint32_t previous_bandwidth{500000};
uint32_t sampling_rate = 0;
uint32_t anti_alias_baseband_bandwidth_filter = 2500000; // we rename the previous var , and change type static constexpr to normal var.
void on_tuning_frequency_changed(rf::Frequency f);
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::Capture};
app_settings::SettingsManager settings_{
"rx_capture", app_settings::Mode::RX,
app_settings::Options::UseGlobalTargetFrequency};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Color::light_grey()},
{{11 * 8, 1 * 16}, "Format:", Color::light_grey()},
};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4},
};
{24 * 8, 0, 6 * 8, 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4},
};
{24 * 8, 5, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
};
nav_};
FrequencyStepView field_frequency_step{
{10 * 8, 0 * 16},
};
{10 * 8, 0 * 16}};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
@@ -101,7 +84,19 @@ class CaptureAppView : public View {
OptionsField option_bandwidth{
{5 * 8, 1 * 16},
5,
{BW_OPTIONS}};
{}};
OptionsField option_format{
{18 * 8, 1 * 16},
3,
{{"C16", RecordView::FileType::RawS16},
{"C8", RecordView::FileType::RawS8}}};
Checkbox check_trim{
{23 * 8, 1 * 16},
4,
"Trim",
/*small*/ true};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
@@ -111,7 +106,7 @@ class CaptureAppView : public View {
16384,
3};
spectrum::WaterfallWidget waterfall{};
spectrum::WaterfallView waterfall{};
};
} /* namespace ui */
+26 -37
View File
@@ -54,20 +54,31 @@ std::string id(ID value) {
}
std::string consumption(Consumption value) {
return to_string_dec_uint(value, 10);
return to_string_dec_uint(value, 8);
}
std::string commodity_type(CommodityType value) {
return to_string_dec_uint(value, 2);
}
std::string tamper_flags(TamperFlags value) {
return to_string_hex(value & 0xFFFF, 4); // Note: ignoring bits 32-47 of tamper flags in IDM type due to screen width
}
std::string tamper_flags_scm(TamperFlags value) {
return " " + to_string_hex(value & 0x0F, 1) + "/" + to_string_hex(value >> 4, 1); // Physical/Encoder flags
}
} /* namespace format */
} /* namespace ert */
void ERTLogger::on_packet(const ert::Packet& packet) {
void ERTLogger::on_packet(const ert::Packet& packet, const uint32_t target_frequency) {
const auto formatted = packet.symbols_formatted();
std::string entry = ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors + " ID:" + to_string_dec_uint(packet.id(), 1);
log_file.write_entry(packet.received_at(), entry);
}
@@ -77,6 +88,8 @@ void ERTRecentEntry::update(const ert::Packet& packet) {
received_count++;
last_consumption = packet.consumption();
last_tamper_flags = packet.tamper_flags();
packet_type = packet.type();
}
namespace ui {
@@ -87,22 +100,21 @@ void RecentEntriesTable<ERTRecentEntries>::draw(
const Rect& target_rect,
Painter& painter,
const Style& style) {
std::string line = ert::format::id(entry.id) + " " + ert::format::commodity_type(entry.commodity_type) + " " + ert::format::consumption(entry.last_consumption);
std::string line = ert::format::id(entry.id) + " " + ert::format::commodity_type(entry.commodity_type) + " " + ert::format::consumption(entry.last_consumption) + " ";
if (entry.received_count > 999) {
line += " +++";
} else {
line += " " + to_string_dec_uint(entry.received_count, 3);
}
line += (entry.packet_type == ert::Packet::Type::SCM) ? ert::format::tamper_flags_scm(entry.last_tamper_flags) : ert::format::tamper_flags(entry.last_tamper_flags);
line += (entry.received_count > 99) ? " ++" : to_string_dec_uint(entry.received_count, 3);
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
ERTAppView::ERTAppView(NavigationView&) {
ERTAppView::ERTAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_ert);
add_children({
&field_frequency,
&field_rf_amp,
&field_lna,
&field_vga,
@@ -110,28 +122,9 @@ ERTAppView::ERTAppView(NavigationView&) {
&recent_entries_view,
});
// load app settings
auto rc = settings.load("rx_ert", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
field_frequency.set_step(1000000);
receiver_model.set_tuning_frequency(initial_target_frequency);
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
/* radio::enable({
initial_target_frequency,
sampling_rate,
baseband_bandwidth,
rf::Direction::Receive,
receiver_model.rf_amp(),
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga()),
}); */
receiver_model.enable();
logger = std::make_unique<ERTLogger>();
if (logger) {
@@ -140,11 +133,7 @@ ERTAppView::ERTAppView(NavigationView&) {
}
ERTAppView::~ERTAppView() {
// save app settings
settings.save("rx_ert", &app_settings);
receiver_model.disable(); // to switch off all, including DC bias and change flag enabled_
receiver_model.disable();
baseband::shutdown();
}
@@ -159,7 +148,7 @@ void ERTAppView::set_parent_rect(const Rect new_parent_rect) {
void ERTAppView::on_packet(const ert::Packet& packet) {
if (logger) {
logger->on_packet(packet);
logger->on_packet(packet, receiver_model.target_frequency());
}
if (packet.crc_ok()) {
+21 -13
View File
@@ -24,11 +24,13 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_rssi.hpp"
#include "ui_channel.hpp"
#include "event_m0.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "ert_packet.hpp"
@@ -70,11 +72,12 @@ struct ERTRecentEntry {
ert::ID id{ert::invalid_id};
ert::CommodityType commodity_type{ert::invalid_commodity_type};
ert::Consumption last_consumption{};
ert::TamperFlags last_tamper_flags{};
ert::Packet::Type packet_type{};
size_t received_count{0};
ert::Consumption last_consumption{};
ERTRecentEntry(
const Key& key)
: id{key.id},
@@ -94,7 +97,7 @@ class ERTLogger {
return log_file.append(filename);
}
void on_packet(const ert::Packet& packet);
void on_packet(const ert::Packet& packet, const uint32_t target_frequency);
private:
LogFile log_file{};
@@ -108,10 +111,6 @@ using ERTRecentEntriesView = RecentEntriesView<ERTRecentEntries>;
class ERTAppView : public View {
public:
static constexpr uint32_t initial_target_frequency = 911600000;
static constexpr uint32_t sampling_rate = 4194304;
static constexpr uint32_t baseband_bandwidth = 2500000;
ERTAppView(NavigationView& nav);
~ERTAppView();
@@ -129,20 +128,29 @@ class ERTAppView : public View {
ERTRecentEntries recent{};
std::unique_ptr<ERTLogger> logger{};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{
911600000 /* frequency */,
2500000 /* bandwidth */,
4194304 /* sampling rate */};
app_settings::SettingsManager settings_{
"rx_ert", app_settings::Mode::RX};
const RecentEntriesColumns columns{{
{"ID", 10},
{"Tp", 2},
{"Consumpt", 10},
{"Cnt", 3},
{"Ty", 2},
{"Consumpt", 8},
{"Tamp", 4},
{"Ct", 2},
}};
ERTRecentEntriesView recent_entries_view{columns, recent};
static constexpr auto header_height = 1 * 16;
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
+272 -163
View File
@@ -22,15 +22,15 @@
#include "pocsag_app.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "utility.hpp"
using namespace portapack;
using namespace pocsag;
#include "string_format.hpp"
#include "utility.hpp"
#include "audio.hpp"
namespace pmem = portapack::persistent_memory;
void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency) {
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz " +
@@ -43,200 +43,309 @@ void POCSAGLogger::log_raw_data(const pocsag::POCSAGPacket& packet, const uint32
log_file.write_entry(packet.timestamp(), entry);
}
void POCSAGLogger::log_decoded(
const pocsag::POCSAGPacket& packet,
const std::string text) {
log_file.write_entry(packet.timestamp(), text);
void POCSAGLogger::log_decoded(Timestamp timestamp, const std::string& text) {
log_file.write_entry(timestamp, text);
}
namespace ui {
void POCSAGAppView::update_freq(rf::Frequency f) {
set_target_frequency(f);
portapack::persistent_memory::set_tuned_frequency(f); // Maybe not ?
POCSAGSettingsView::POCSAGSettingsView(
NavigationView& nav,
POCSAGSettings& settings)
: settings_{settings} {
add_children(
{&labels,
&check_log,
&check_log_raw,
&check_small_font,
&check_hide_bad,
&check_hide_addr_only,
&opt_filter_mode,
&field_filter_address,
&button_save});
check_log.set_value(settings_.enable_logging);
check_log_raw.set_value(settings_.enable_raw_log);
check_small_font.set_value(settings_.enable_small_font);
check_hide_bad.set_value(settings_.hide_bad_data);
check_hide_addr_only.set_value(settings_.hide_addr_only);
opt_filter_mode.set_by_value(settings_.filter_mode);
field_filter_address.set_value(settings_.filter_address);
button_save.on_select = [this, &nav](Button&) {
settings_.enable_logging = check_log.value();
settings_.enable_raw_log = check_log_raw.value();
settings_.enable_small_font = check_small_font.value();
settings_.hide_bad_data = check_hide_bad.value();
settings_.hide_addr_only = check_hide_addr_only.value();
settings_.filter_mode = opt_filter_mode.selected_index_value();
settings_.filter_address = field_filter_address.to_integer();
nav.pop();
};
}
POCSAGAppView::POCSAGAppView(NavigationView& nav) {
uint32_t ignore_address;
POCSAGAppView::POCSAGAppView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_pocsag2);
baseband::run_image(portapack::spi_flash::image_tag_pocsag);
add_children(
{&rssi,
&audio,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&field_squelch,
&field_volume,
&image_status,
&text_packet_count,
&widget_baud,
&widget_bits,
&widget_frames,
&button_filter_last,
&button_config,
&console});
add_children({&rssi,
&channel,
&audio,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&check_log,
&field_volume,
&check_ignore,
&sym_ignore,
&console});
// Set on_change before initialising the field
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
// load app settings
auto rc = settings.load("rx_pocsag", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
field_frequency.set_value(app_settings.rx_frequency);
} else
field_frequency.set_value(receiver_model.tuning_frequency());
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
check_ignore.set_value(ignore);
check_ignore.on_select = [this](Checkbox&, bool v) {
ignore = v;
};
ignore_address = persistent_memory::pocsag_ignore_address();
for (size_t c = 0; c < 7; c++) {
sym_ignore.set_sym(6 - c, ignore_address % 10);
ignore_address /= 10;
// No app settings, use fallbacks from pmem.
if (!app_settings_.loaded()) {
settings_.filter_address = pmem::pocsag_ignore_address();
settings_.filter_mode = (settings_.filter_address == 0)
? FILTER_NONE
: FILTER_DROP;
}
logger = std::make_unique<POCSAGLogger>();
if (logger)
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
logger.append(LOG_ROOT_DIR "/POCSAG.TXT");
field_squelch.set_value(receiver_model.squelch_level());
field_squelch.on_change = [this](int32_t v) {
receiver_model.set_squelch_level(v);
};
button_filter_last.on_select = [this](Button&) {
if (settings_.filter_mode == FILTER_NONE)
settings_.filter_mode = FILTER_DROP;
settings_.filter_address = last_address;
refresh_ui();
};
button_config.on_select = [this](Button&) {
nav_.push<POCSAGSettingsView>(settings_);
nav_.set_on_pop([this]() { refresh_ui(); });
};
refresh_ui();
audio::output::start();
audio::output::unmute();
receiver_model.enable();
baseband::set_pocsag();
}
POCSAGAppView::~POCSAGAppView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
settings.save("rx_pocsag", &app_settings);
audio::output::stop();
// Save ignored address
persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
receiver_model.disable();
baseband::shutdown();
}
void POCSAGAppView::focus() {
field_frequency.focus();
}
void POCSAGAppView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
POCSAGAppView::~POCSAGAppView() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
// Save pmem settings.
pmem::set_pocsag_ignore_address(settings_.filter_address);
pmem::set_pocsag_last_address(last_address); // For POCSAG TX.
}
// Useless ?
void POCSAGAppView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
void POCSAGAppView::refresh_ui() {
// Set console font style.
console.set_style(
settings_.enable_small_font
? &Styles::white_small
: &Styles::white);
// Update filter button text.
std::string btn_text = "Filter Last";
switch (settings_.filter_mode) {
case FILTER_DROP:
btn_text = "Ignore Last";
break;
case FILTER_KEEP:
btn_text = "Keep Last";
break;
case FILTER_NONE:
default:
btn_text = "Filter Last";
break;
}
button_filter_last.set_text(btn_text);
}
bool POCSAGAppView::ignore_address(uint32_t address) const {
switch (settings_.filter_mode) {
case FILTER_DROP:
return address == settings_.filter_address;
case FILTER_KEEP:
return address != settings_.filter_address;
case FILTER_NONE:
default:
return false;
}
}
void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
bool bad_data = pocsag_state.errors >= 3;
// Too many errors for reliable decode.
if (bad_data && hide_bad_data()) {
console.write("\n" STR_COLOR_MAGENTA + prefix + " Too many decode errors.");
last_address = 0;
return;
}
// Ignore address? TODO: could filter earlier.
if (ignore_address(pocsag_state.address)) {
console.write("\n" STR_COLOR_CYAN + prefix + " Ignored: " + to_string_dec_uint(pocsag_state.address));
last_address = pocsag_state.address;
return;
}
// Color indicates the message has a lot of decoding errors.
std::string color = bad_data ? STR_COLOR_MAGENTA : STR_COLOR_WHITE;
std::string console_info = "\n" + color + prefix;
console_info += " #" + to_string_dec_uint(pocsag_state.address);
console_info += " F" + to_string_dec_uint(pocsag_state.function);
if (pocsag_state.out_type == ADDRESS) {
last_address = pocsag_state.address;
if (!hide_addr_only()) {
console.write(console_info);
if (logging()) {
logger.log_decoded(
timestamp,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function));
}
}
} else if (pocsag_state.out_type == MESSAGE) {
if (pocsag_state.address != last_address) {
// New message
last_address = pocsag_state.address;
console.writeln(console_info);
console.write(color + pocsag_state.output);
} else {
// Message continues...
console.write(color + pocsag_state.output);
}
if (logging()) {
logger.log_decoded(
timestamp,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" " + pocsag_state.output);
}
}
}
static Color get_status_color(const POCSAGState& state) {
if (state.out_type == IDLE)
return Color::white();
switch (state.mode) {
case STATE_CLEAR:
return Color::cyan();
case STATE_HAVE_ADDRESS:
return Color::yellow();
case STATE_GETTING_MSG:
return Color::green();
}
// Shouldn't get here...
return Color::red();
}
void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
std::string alphanum_text = "";
const uint32_t roundVal = 50;
const uint32_t bitrate_rounded = roundVal * ((message->packet.bitrate() + (roundVal / 2)) / roundVal);
auto bitrate = to_string_dec_uint(bitrate_rounded);
auto timestamp = to_string_datetime(message->packet.timestamp(), HM);
auto prefix = timestamp + " " + bitrate;
if (message->packet.flag() != NORMAL)
console.writeln("\n\x1B\x0CRC ERROR: " + pocsag::flag_str(message->packet.flag()));
else {
pocsag_decode_batch(message->packet, &pocsag_state);
// Display packet count to be able to tell whether baseband sent a packet for a tone.
++packet_count;
text_packet_count.set(to_string_dec_uint(packet_count));
if ((ignore) && (pocsag_state.address == sym_ignore.value_dec_u32())) {
// Ignore (inform, but no log)
// console.write("\n\x1B\x03" + to_string_time(message->packet.timestamp()) +
// " Ignored address " + to_string_dec_uint(pocsag_state.address));
return;
}
// Too many errors for reliable decode
if ((ignore) && (pocsag_state.errors >= 3)) {
return;
}
if (logging_raw())
logger.log_raw_data(message->packet, receiver_model.target_frequency());
std::string console_info;
const uint32_t roundVal = 50;
const uint32_t bitrate = roundVal * ((message->packet.bitrate() + (roundVal / 2)) / roundVal);
console_info = "\n" + to_string_datetime(message->packet.timestamp(), HM);
console_info += " " + to_string_dec_uint(bitrate);
console_info += " ADDR:" + to_string_dec_uint(pocsag_state.address);
console_info += " F" + to_string_dec_uint(pocsag_state.function);
if (message->packet.flag() != NORMAL) {
console.writeln("\n" STR_COLOR_RED + prefix + " CRC ERROR: " + pocsag::flag_str(message->packet.flag()));
last_address = 0;
} else {
// Set color before to be able to see if decode gets stuck.
image_status.set_foreground(Color::magenta());
pocsag_state.codeword_index = 0;
pocsag_state.errors = 0;
// Store last received address for POCSAG TX
persistent_memory::set_pocsag_last_address(pocsag_state.address);
// Handle multiple messages (if any).
while (pocsag_decode_batch(message->packet, pocsag_state))
handle_decoded(message->packet.timestamp(), prefix);
if (pocsag_state.out_type == ADDRESS) {
// Address only
console.write(console_info);
if (logger && logging) {
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Address only");
}
last_address = pocsag_state.address;
} else if (pocsag_state.out_type == MESSAGE) {
if (pocsag_state.address != last_address) {
// New message
console.writeln(console_info);
console.write(pocsag_state.output);
last_address = pocsag_state.address;
} else {
// Message continues...
console.write(pocsag_state.output);
}
if (logger && logging)
logger->log_decoded(message->packet, to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" Alpha: " + pocsag_state.output);
}
// Handle the remainder.
handle_decoded(message->packet.timestamp(), prefix);
}
// Log raw data whatever it contains
if (logger && logging)
logger->log_raw_data(message->packet, target_frequency());
// Set status icon color to indicate state machine state.
image_status.set_foreground(get_status_color(pocsag_state));
}
void POCSAGAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
receiver_model.set_tuning_frequency(new_value);
void POCSAGAppView::on_stats(const POCSAGStatsMessage* stats) {
widget_baud.set_rate(stats->baud_rate);
widget_bits.set_bits(stats->current_bits);
widget_frames.set_frames(stats->current_frames);
widget_frames.set_sync(stats->has_sync);
}
uint32_t POCSAGAppView::target_frequency() const {
return target_frequency_;
void BaudIndicator::paint(Painter& painter) {
auto p = screen_pos();
char top = '-';
char bot = '-';
if (rate_ > 0) {
auto r = rate_ / 100;
top = (r / 10) + '0';
bot = (r % 10) + '0';
}
painter.draw_char(p, Styles::white_small, top);
painter.draw_char({p.x(), p.y() + 8}, Styles::white_small, bot);
}
void BitsIndicator::paint(Painter&) {
auto p = screen_pos();
for (size_t i = 0; i < sizeof(bits_) * 8; ++i) {
auto is_set = ((bits_ >> i) & 0x1) == 1;
int x = p.x() + (i / height);
int y = p.y() + (i % height);
display.draw_pixel({x, y}, is_set ? Color::white() : Color::black());
}
}
void FrameIndicator::paint(Painter& painter) {
auto p = screen_pos();
painter.draw_rectangle({p, {2, height}}, has_sync_ ? Color::green() : Color::grey());
for (size_t i = 0; i < height; ++i) {
auto p2 = p + Point{2, 15 - (int)i};
painter.draw_hline(p2, 2, i < frame_count_ ? Color::white() : Color::black());
}
}
} /* namespace ui */
+232 -59
View File
@@ -24,13 +24,18 @@
#define __POCSAG_APP_H__
#include "ui_widget.hpp"
#include "ui_freq_field.hpp"
#include "ui_receiver.hpp"
#include "ui_rssi.hpp"
#include "ui_styles.hpp"
#include "log_file.hpp"
#include "app_settings.hpp"
#include "log_file.hpp"
#include "pocsag.hpp"
#include "pocsag_packet.hpp"
#include "radio_state.hpp"
#include <functional>
class POCSAGLogger {
public:
@@ -39,7 +44,7 @@ class POCSAGLogger {
}
void log_raw_data(const pocsag::POCSAGPacket& packet, const uint32_t frequency);
void log_decoded(const pocsag::POCSAGPacket& packet, const std::string text);
void log_decoded(Timestamp timestamp, const std::string& text);
private:
LogFile log_file{};
@@ -47,85 +52,246 @@ class POCSAGLogger {
namespace ui {
class BaudIndicator : public Widget {
public:
BaudIndicator(Point position)
: Widget{{position, {5, height}}} {}
void paint(Painter& painter) override;
void set_rate(uint16_t rate) {
if (rate != rate_) {
rate_ = rate;
set_dirty();
}
}
private:
static constexpr uint8_t height = 16;
uint16_t rate_ = 0;
};
class BitsIndicator : public Widget {
public:
BitsIndicator(Point position)
: Widget{{position, {2, height}}} {}
void paint(Painter& painter) override;
void set_bits(uint32_t bits) {
if (bits != bits_) {
bits_ = bits;
set_dirty();
}
}
private:
static constexpr uint8_t height = 16;
uint32_t bits_ = 0;
};
class FrameIndicator : public Widget {
public:
FrameIndicator(Point position)
: Widget{{position, {4, height}}} {}
void paint(Painter& painter) override;
void set_frames(uint8_t frame_count) {
if (frame_count != frame_count_) {
frame_count_ = frame_count;
set_dirty();
}
}
void set_sync(bool has_sync) {
if (has_sync != has_sync_) {
has_sync_ = has_sync;
set_dirty();
}
}
private:
static constexpr uint8_t height = 16;
uint8_t frame_count_ = 0;
bool has_sync_ = false;
};
enum POCSAGFilter : uint8_t {
FILTER_NONE,
FILTER_DROP,
FILTER_KEEP
};
struct POCSAGSettings {
bool enable_small_font = false;
bool enable_logging = false;
bool enable_raw_log = false;
bool hide_bad_data = false;
bool hide_addr_only = false;
uint8_t filter_mode = false;
uint32_t filter_address = 0;
};
class POCSAGSettingsView : public View {
public:
POCSAGSettingsView(NavigationView& nav, POCSAGSettings& settings);
std::string title() const override { return "POCSAG Config"; };
void focus() override { button_save.focus(); }
private:
POCSAGSettings& settings_;
Labels labels{
{{2 * 8, 12 * 16}, "Filter Mode:", Color::light_grey()},
{{2 * 8, 13 * 16}, "Filter Addr:", Color::light_grey()},
};
Checkbox check_log{
{2 * 8, 2 * 16},
10,
"Enable Log"};
Checkbox check_log_raw{
{2 * 8, 4 * 16},
12,
"Log Raw Data"};
Checkbox check_small_font{
{2 * 8, 6 * 16},
4,
"Use Small Font"};
Checkbox check_hide_bad{
{2 * 8, 8 * 16},
22,
"Hide Bad Data"};
Checkbox check_hide_addr_only{
{2 * 8, 10 * 16},
22,
"Hide Addr Only"};
OptionsField opt_filter_mode{
{15 * 8, 12 * 16},
4,
{{"None", FILTER_NONE},
{"Drop", FILTER_DROP},
{"Keep", FILTER_KEEP}}};
SymField field_filter_address{
{15 * 8, 13 * 16},
7,
SymField::Type::Dec,
true /*explicit_edit*/};
Button button_save{
{11 * 8, 16 * 16, 10 * 8, 2 * 16},
"Save"};
};
class POCSAGAppView : public View {
public:
POCSAGAppView(NavigationView& nav);
~POCSAGAppView();
void set_parent_rect(const Rect new_parent_rect) override;
std::string title() const override { return "POCSAG RX"; };
void focus() override;
std::string title() const override { return "POCSAG RX"; };
private:
static constexpr uint32_t initial_target_frequency = 466175000;
bool logging() const { return settings_.enable_logging; };
bool logging_raw() const { return settings_.enable_raw_log; };
bool hide_bad_data() const { return settings_.hide_bad_data; };
bool hide_addr_only() const { return settings_.hide_addr_only; };
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{
466175000 /* frequency*/,
max283x::filter::bandwidth_minimum /* bandwidth */,
3072000 /* sampling rate */
};
bool logging{false};
bool ignore{false};
uint32_t last_address = 0xFFFFFFFF;
pocsag::POCSAGState pocsag_state{};
// Settings
POCSAGSettings settings_{};
app_settings::SettingsManager app_settings_{
"rx_pocsag"sv,
app_settings::Mode::RX,
{
{"small_font"sv, &settings_.enable_small_font},
{"enable_logging"sv, &settings_.enable_logging},
{"enable_raw_log"sv, &settings_.enable_raw_log},
{"filter_mode"sv, &settings_.filter_mode},
{"filter_address"sv, &settings_.filter_address},
{"hide_bad_data"sv, &settings_.hide_bad_data},
{"hide_addr_only"sv, &settings_.hide_addr_only},
}};
void refresh_ui();
bool ignore_address(uint32_t address) const;
void handle_decoded(Timestamp timestamp, const std::string& prefix);
void on_packet(const POCSAGPacketMessage* message);
void on_stats(const POCSAGStatsMessage* stats);
uint32_t last_address = 0;
pocsag::EccContainer ecc{};
pocsag::POCSAGState pocsag_state{&ecc};
POCSAGLogger logger{};
uint16_t packet_count = 0;
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
nav_};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{11 * 8, 0 * 16}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{13 * 8, 0 * 16}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
Audio audio{
{21 * 8, 10, 6 * 8, 4},
};
{16 * 8, 0 * 16}};
FrequencyField field_frequency{
{0 * 8, 0 * 8},
};
NumberField field_volume{
{28 * 8, 0 * 16},
RSSI rssi{
{19 * 8 - 4, 3, 6 * 8, 4}};
Audio audio{
{19 * 8 - 4, 8, 6 * 8, 4}};
NumberField field_squelch{
{25 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
true /*wrap*/};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
Checkbox check_ignore{
{0 * 8, 21},
8,
"Ign addr",
false};
SymField sym_ignore{
{13 * 8, 25},
7,
SymField::SYMFIELD_DEC};
Checkbox check_log{
{240 - 8 * 8, 21},
3,
"LOG",
false};
Image image_status{
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
&bitmap_icon_pocsag,
Color::white(),
Color::black()};
Text text_packet_count{
{3 * 8, 1 * 16 + 2, 5 * 8, 16},
"0"};
BitsIndicator widget_bits{
{8 * 8 + 1, 1 * 16 + 2}};
FrameIndicator widget_frames{
{8 * 8 + 4, 1 * 16 + 2}};
BaudIndicator widget_baud{
{8 * 9 + 1, 1 * 16 + 2}};
Button button_filter_last{
{10 * 8, 1 * 16, 12 * 8, 20},
"Filter Last"};
Button button_config{
{22 * 8, 1 * 16, 8 * 8, 20},
"Config"};
// 54 == status bar (16) + top controls (2 * 16 + 6).
Console console{
{0, 3 * 16, 240, 256}};
std::unique_ptr<POCSAGLogger> logger{};
uint32_t target_frequency_ = initial_target_frequency;
void update_freq(rf::Frequency f);
void on_packet(const POCSAGPacketMessage* message);
void on_headphone_volume_changed(int32_t v);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
{0, 2 * 16 + 6, screen_width, screen_height - 54}};
MessageHandlerRegistration message_handler_packet{
Message::ID::POCSAGPacket,
@@ -133,6 +299,13 @@ class POCSAGAppView : public View {
const auto message = static_cast<const POCSAGPacketMessage*>(p);
this->on_packet(message);
}};
MessageHandlerRegistration message_handler_stats{
Message::ID::POCSAGStats,
[this](Message* const p) {
const auto stats = static_cast<const POCSAGStatsMessage*>(p);
this->on_stats(stats);
}};
};
} /* namespace ui */
+46 -95
View File
@@ -26,12 +26,16 @@
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "cpld_update.hpp"
#include "io_convert.hpp"
#include "baseband_api.hpp"
#include "metadata_file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "utility.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
@@ -39,51 +43,42 @@ void ReplayAppView::set_ready() {
ready_signal = true;
}
void ReplayAppView::on_file_changed(std::filesystem::path new_file_path) {
File data_file, info_file;
char file_data[257];
void ReplayAppView::on_file_changed(const fs::path& new_file_path) {
file_path = fs::path(u"/") + new_file_path;
File::Size file_size{};
// Get file size
auto data_open_error = data_file.open("/" + new_file_path.string());
if (data_open_error.is_valid()) {
file_error();
return;
}
file_path = new_file_path;
// Get original record frequency if available
std::filesystem::path info_file_path = file_path;
info_file_path.replace_extension(u".TXT");
sample_rate = 500000;
auto info_open_error = info_file.open("/" + info_file_path.string());
if (!info_open_error.is_valid()) {
memset(file_data, 0, 257);
auto read_size = info_file.read(file_data, 256);
if (!read_size.is_error()) {
auto pos1 = strstr(file_data, "center_frequency=");
if (pos1) {
pos1 += 17;
field_frequency.set_value(strtoll(pos1, nullptr, 10));
}
auto pos2 = strstr(file_data, "sample_rate=");
if (pos2) {
pos2 += 12;
sample_rate = strtoll(pos2, nullptr, 10);
}
{ // Get the size of the data file.
File data_file;
auto error = data_file.open(file_path);
if (error) {
file_error();
return;
}
file_size = data_file.size();
}
// Get original record frequency if available.
auto metadata_path = get_metadata_path(file_path);
auto metadata = read_metadata_file(metadata_path);
if (metadata) {
field_frequency.set_value(metadata->center_frequency);
sample_rate = metadata->sample_rate;
} else {
// TODO: This is interesting because it implies that the
// The capture will just be replayed at the freq set on the
// FrequencyField. Is that an intentional behavior?
sample_rate = 500000;
}
// UI Fixup.
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
auto file_size = data_file.size();
auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
progressbar.set_max(file_size);
text_filename.set(file_path.filename().string().substr(0, 12));
text_filename.set(truncate(file_path.filename().string(), 12));
uint8_t sample_size = capture_file_sample_size(current()->path);
auto duration = ms_duration(file_size, sample_rate, sample_size);
text_duration.set(to_string_time_ms(duration));
button_play.focus();
@@ -118,7 +113,7 @@ void ReplayAppView::start() {
std::unique_ptr<stream::Reader> reader;
auto p = std::make_unique<FileReader>();
auto p = std::make_unique<FileConvertReader>();
auto open_error = p->open(file_path);
if (open_error.is_valid()) {
file_error();
@@ -129,7 +124,7 @@ void ReplayAppView::start() {
if (reader) {
button_play.set_bitmap(&bitmap_stop);
baseband::set_sample_rate(sample_rate * 8);
baseband::set_sample_rate(sample_rate, OversampleRate::x8);
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
@@ -141,18 +136,9 @@ void ReplayAppView::start() {
});
}
// Enable Bias Tee if selected
radio::set_antenna_bias(portapack::get_antenna_bias());
rf_amp = (transmitter_model.rf_amp()); // recover rf_amp settings applied from ui_transmiter.cpp
radio::enable({receiver_model.tuning_frequency(),
sample_rate * 8,
baseband_bandwidth,
rf::Direction::Transmit,
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga())});
transmitter_model.set_sampling_rate(sample_rate * toUType(OversampleRate::x8));
transmitter_model.set_baseband_bandwidth(baseband_bandwidth);
transmitter_model.enable();
if (portapack::persistent_memory::stealth_mode()) {
DisplaySleepMessage message;
@@ -167,8 +153,7 @@ void ReplayAppView::stop(const bool do_loop) {
if (do_loop && check_loop.value()) {
start();
} else {
radio::set_antenna_bias(false); // Turn off Bias Tee
radio::disable();
transmitter_model.disable();
button_play.set_bitmap(&bitmap_play);
}
@@ -198,48 +183,27 @@ ReplayAppView::ReplayAppView(
&text_duration,
&progressbar,
&field_frequency,
&tx_view, // now it handles previous rfgain , rfamp.
&tx_view, // now it handles previous rfgain, rfamp.
&check_loop,
&button_play,
&waterfall,
});
field_frequency.set_value(target_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
this->field_frequency.set_value(f);
};
};
field_frequency.set_step(5000);
button_play.on_select = [this](ImageButton&) {
this->toggle();
};
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".C16");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
auto open_view = nav.push<FileLoadView>(".C*");
open_view->on_changed = [this](fs::path new_file_path) {
on_file_changed(new_file_path);
};
};
}
ReplayAppView::~ReplayAppView() {
radio::disable();
display.fill_rectangle({0, 0, 240, 320}, Color::black()); // Solving sometimes visible bottom waterfall artifacts, clearing all LCD pixels.
chThdSleepMilliseconds(40); // (that happened sometimes if we interrupt the waterfall play at the beggining of the play around 25% and exit )
hackrf::cpld::load_sram_no_verify(); // to leave all RX reception ok, without "ghost interference signal problem" at the exit .
baseband::shutdown(); // better this function at the end, after load_sram(). If not , sometimes produced hang up (now not , it is ok).
transmitter_model.disable();
baseband::shutdown();
}
void ReplayAppView::on_hide() {
@@ -257,17 +221,4 @@ void ReplayAppView::set_parent_rect(const Rect new_parent_rect) {
waterfall.set_parent_rect(waterfall_rect);
}
void ReplayAppView::on_target_frequency_changed(rf::Frequency f) {
set_target_frequency(f);
}
void ReplayAppView::set_target_frequency(const rf::Frequency new_value) {
persistent_memory::set_tuned_frequency(new_value);
;
}
rf::Frequency ReplayAppView::target_frequency() const {
return persistent_memory::tuned_frequency();
}
} /* namespace ui */
+13 -16
View File
@@ -23,18 +23,18 @@
#ifndef __REPLAY_APP_HPP__
#define __REPLAY_APP_HPP__
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "replay_thread.hpp"
#include "ui_spectrum.hpp"
#include "ui_transmitter.hpp"
#include <string>
#include <memory>
#include "ui_transmitter.hpp"
namespace ui {
@@ -51,6 +51,9 @@ class ReplayAppView : public View {
private:
NavigationView& nav_;
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_replay", app_settings::Mode::TX};
static constexpr ui::Dim header_height = 3 * 16;
@@ -61,13 +64,9 @@ class ReplayAppView : public View {
const size_t read_size{16384};
const size_t buffer_count{3};
void on_file_changed(std::filesystem::path new_file_path);
void on_target_frequency_changed(rf::Frequency f);
void on_file_changed(const std::filesystem::path& new_file_path);
void on_tx_progress(const uint32_t progress);
void set_target_frequency(const rf::Frequency new_value);
rf::Frequency target_frequency() const;
void toggle();
void start();
void stop(const bool do_loop);
@@ -97,15 +96,13 @@ class ReplayAppView : public View {
ProgressBar progressbar{
{18 * 8, 1 * 16, 12 * 8, 16}};
FrequencyField field_frequency{
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
};
nav_};
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
74, 1 * 8, SHORT_UI // x(columns), y (line) position. (Uused in Repay / GPS Simul / Playlist App)
// 10*8, 2*8, NORMAL_UI // x(columns), y (line) position. (Used in Mic App)
};
{11 * 8, 2 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
@@ -118,7 +115,7 @@ class ReplayAppView : public View {
Color::green(),
Color::black()};
spectrum::WaterfallWidget waterfall{};
spectrum::WaterfallView waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
+10 -21
View File
@@ -25,7 +25,8 @@
#include "soundboard_app.hpp"
#include "string_format.hpp"
#include "tonesets.hpp"
#include "cpld_update.hpp"
#include "ui_tone_key.hpp"
#include "audio.hpp"
using namespace tonekey;
using namespace portapack;
@@ -40,6 +41,7 @@ void SoundBoardView::stop() {
if (is_active())
replay_thread.reset();
audio::output::stop();
transmitter_model.disable();
tx_view.set_transmitting(false);
@@ -106,6 +108,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
EventDispatcher::send_message(message);
});
// TODO: Delete all this and use tx model.
baseband::set_audiotx_config(
1536000 / 20, // Update vu-meter at 20Hz
transmitter_model.channel_bandwidth(),
@@ -119,11 +122,11 @@ void SoundBoardView::start_tx(const uint32_t id) {
);
baseband::set_sample_rate(sample_rate);
transmitter_model.set_sampling_rate(1536000);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
tx_view.set_transmitting(true);
audio::output::start();
}
/*void SoundBoardView::show_infos() {
@@ -228,6 +231,7 @@ SoundBoardView::SoundBoardView(
//&text_title,
//&text_duration,
//&progressbar,
&field_volume,
&page_info,
&check_loop,
&check_random,
@@ -235,15 +239,6 @@ SoundBoardView::SoundBoardView(
&button_next_page,
&tx_view});
// load app settings
auto rc = settings.load("tx_soundboard", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
refresh_list();
button_next_page.on_select = [this](Button&) {
@@ -266,9 +261,9 @@ SoundBoardView::SoundBoardView(
check_random.set_value(false);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
@@ -283,14 +278,8 @@ SoundBoardView::SoundBoardView(
}
SoundBoardView::~SoundBoardView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_soundboard", &app_settings);
stop();
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
} // namespace ui
+14 -7
View File
@@ -29,8 +29,8 @@
#include "baseband_api.hpp"
#include "lfsr_random.hpp"
#include "io_wave.hpp"
#include "tone_key.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -49,11 +49,15 @@ class SoundBoardView : public View {
std::string title() const override { return "Soundbrd TX"; };
private:
NavigationView& nav_;
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_soundboard", app_settings::Mode::TX};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
enum tx_modes {
NORMAL = 0,
@@ -87,7 +91,7 @@ class SoundBoardView : public View {
void on_select_entry();
Labels labels{
//{ { 0, 20 * 8 + 4 }, "Title:", Color::light_grey() },
{{24 * 8, 180}, "Vol:", Color::light_grey()},
{{0, 180}, "Key:", Color::light_grey()}};
Button button_next_page{
@@ -118,10 +122,13 @@ class SoundBoardView : public View {
};*/
OptionsField options_tone_key{
{32, 180},
{4 * 8, 180},
18,
{}};
AudioVolumeField field_volume{
{28 * 8, 180}};
Checkbox check_loop{
{0, 25 * 8 + 4},
4,
@@ -32,7 +32,7 @@ class SpectrumAnalysisModel {
namespace ui {
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
class SpectrumAnalysisView : public spectrum::WaterfallView {
public:
private:
SpectrumAnalysisModel model;
+13 -63
View File
@@ -34,9 +34,6 @@ namespace tpms {
namespace format {
static bool use_kpa = true;
static bool use_celsius = true;
std::string type(Reading::Type type) {
return to_string_dec_uint(toUType(type), 2);
}
@@ -46,11 +43,11 @@ std::string id(TransponderID id) {
}
std::string pressure(Pressure pressure) {
return to_string_dec_int(use_kpa ? pressure.kilopascal() : pressure.psi(), 3);
return to_string_dec_int(units_psi ? pressure.psi() : pressure.kilopascal(), 3);
}
std::string temperature(Temperature temperature) {
return to_string_dec_int(use_celsius ? temperature.celsius() : temperature.fahrenheit(), 3);
return to_string_dec_int(units_fahr ? temperature.fahrenheit() : temperature.celsius(), 3);
}
std::string flags(Flags flags) {
@@ -78,9 +75,9 @@ void TPMSLogger::on_packet(const tpms::Packet& packet, const uint32_t target_fre
const auto hex_formatted = packet.symbols_formatted();
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
const auto tuning_frequency_str = to_string_dec_uint(target_frequency, 10);
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
std::string entry = tuning_frequency_str + " " + tpms::format::signal_type(packet.signal_type()) + " " + hex_formatted.data + "/" + hex_formatted.errors;
std::string entry = target_frequency_str + " " + tpms::format::signal_type(packet.signal_type()) + " " + hex_formatted.data + "/" + hex_formatted.errors;
log_file.write_entry(packet.received_at(), entry);
}
@@ -157,49 +154,24 @@ TPMSAppView::TPMSAppView(NavigationView&) {
&field_vga,
&recent_entries_view});
// load app settings
auto rc = settings.load("rx_tpms", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
options_band.set_by_value(app_settings.rx_frequency);
} else
options_band.set_by_value(receiver_model.tuning_frequency());
receiver_model.set_tuning_frequency(tuning_frequency());
receiver_model.set_sampling_rate(sampling_rate);
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
receiver_model.enable(); // Before using radio::enable(), but not updating Ant.DC-Bias.
/* radio::enable({
tuning_frequency(),
sampling_rate,
baseband_bandwidth,
rf::Direction::Receive,
receiver_model.rf_amp(),
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga()),
}); */
receiver_model.enable();
options_band.on_change = [this](size_t, OptionsField::value_t v) {
this->on_band_changed(v);
receiver_model.set_target_frequency(v);
};
options_band.set_by_value(target_frequency());
options_band.set_by_value(receiver_model.target_frequency());
options_pressure.on_change = [this](size_t, int32_t i) {
tpms::format::use_kpa = !i;
tpms::format::units_psi = (bool)i;
update_view();
};
options_pressure.set_selected_index(0, true);
options_pressure.set_selected_index(tpms::format::units_psi, true);
options_temperature.on_change = [this](size_t, int32_t i) {
tpms::format::use_celsius = !i;
tpms::format::units_fahr = (bool)i;
update_view();
};
options_temperature.set_selected_index(0, true);
options_temperature.set_selected_index(tpms::format::units_fahr, true);
logger = std::make_unique<TPMSLogger>();
if (logger) {
@@ -208,12 +180,7 @@ TPMSAppView::TPMSAppView(NavigationView&) {
}
TPMSAppView::~TPMSAppView() {
// save app settings
app_settings.rx_frequency = target_frequency_;
settings.save("rx_tpms", &app_settings);
receiver_model.disable(); // to switch off all, including DC bias and change flag enabled_
receiver_model.disable();
baseband::shutdown();
}
@@ -235,7 +202,7 @@ void TPMSAppView::set_parent_rect(const Rect new_parent_rect) {
void TPMSAppView::on_packet(const tpms::Packet& packet) {
if (logger) {
logger->on_packet(packet, target_frequency());
logger->on_packet(packet, receiver_model.target_frequency());
}
const auto reading_opt = packet.reading();
@@ -252,21 +219,4 @@ void TPMSAppView::on_show_list() {
recent_entries_view.focus();
}
void TPMSAppView::on_band_changed(const uint32_t new_band_frequency) {
set_target_frequency(new_band_frequency);
}
void TPMSAppView::set_target_frequency(const uint32_t new_value) {
target_frequency_ = new_value;
radio::set_tuning_frequency(tuning_frequency());
}
uint32_t TPMSAppView::target_frequency() const {
return target_frequency_;
}
uint32_t TPMSAppView::tuning_frequency() const {
return target_frequency() - (sampling_rate / 4);
}
} /* namespace ui */
+29 -24
View File
@@ -28,6 +28,7 @@
#include "ui_rssi.hpp"
#include "ui_channel.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "event_m0.hpp"
#include "log_file.hpp"
@@ -36,11 +37,18 @@
#include "tpms_packet.hpp"
namespace std {
namespace tpms {
constexpr bool operator==(const tpms::TransponderID& lhs, const tpms::TransponderID& rhs) {
return (lhs.value() == rhs.value());
}
namespace format {
static bool units_psi{false};
static bool units_fahr{false};
} /* namespace format */
} /* namespace tpms */
namespace std {
} /* namespace std */
@@ -105,13 +113,18 @@ class TPMSAppView : public View {
std::string title() const override { return "TPMS RX"; };
private:
static constexpr uint32_t initial_target_frequency = 315000000;
static constexpr uint32_t sampling_rate = 2457600;
static constexpr uint32_t baseband_bandwidth = 1750000;
RxRadioState radio_state_{
314950000 /* frequency*/,
1750000 /* bandwidth */,
2457600 /* sampling rate */};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
app_settings::SettingsManager settings_{
"rx_tpms",
app_settings::Mode::RX,
{
{"units_psi"sv, &tpms::format::units_psi},
{"units_fahr"sv, &tpms::format::units_fahr},
}};
MessageHandlerRegistration message_handler_packet{
Message::ID::TPMSPacket,
@@ -133,12 +146,13 @@ class TPMSAppView : public View {
{21 * 8, 5, 6 * 8, 4},
};
// "315 MHz" TPMS sensors transmit at either 314.9 or 315 MHz but we should pick up either
OptionsField options_band{
{0 * 8, 0 * 16},
3,
{
{"315", 315000000},
{"433", 433920000},
{"315", 314950000},
{"434", 433920000},
}};
OptionsField options_pressure{
@@ -149,9 +163,9 @@ class TPMSAppView : public View {
OptionsField options_temperature{
{9 * 8, 0 * 16},
1,
{{"C", 0},
{"F", 1}}};
2,
{{STR_DEGREES_C, 0},
{STR_DEGREES_F, 1}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
@@ -175,18 +189,9 @@ class TPMSAppView : public View {
}};
TPMSRecentEntriesView recent_entries_view{columns, recent};
uint32_t target_frequency_ = initial_target_frequency;
void on_packet(const tpms::Packet& packet);
void on_show_list();
void update_view();
void on_band_changed(const uint32_t new_band_frequency);
uint32_t target_frequency() const;
void set_target_frequency(const uint32_t new_value);
uint32_t tuning_frequency() const;
};
} /* namespace ui */
-1
View File
@@ -25,7 +25,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstdint>
+2 -3
View File
@@ -47,7 +47,7 @@ using namespace portapack;
namespace ui {
void AboutView::on_show() {
transmitter_model.set_tuning_frequency(1337000000); // TODO: Change
transmitter_model.set_target_frequency(1337000000); // TODO: Change
transmitter_model.set_baseband_configuration({
.mode = 0,
.sampling_rate = 1536000,
@@ -56,7 +56,6 @@ void AboutView::on_show() {
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_audiotx_data(32, 50, false, 0);
@@ -178,7 +177,7 @@ void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color* pal) {
int16_t lbx, il;
// Map ASCII to font bitmap
if ((ch >= 32) || (ch < 96))
if ((ch >= 32) && (ch < 96))
che = char_map[ch - 32];
else
che = 0xFF;
+16 -11
View File
@@ -8,7 +8,7 @@ AboutView::AboutView(NavigationView& nav) {
nav.pop();
};
console.writeln("\x1B\x07List of contributors:\x1B\x10");
console.writeln(STR_COLOR_LIGHT_GREY "List of contributors:");
console.writeln("");
}
@@ -19,8 +19,8 @@ void AboutView::update() {
switch (++frame) {
case 1:
// TODO: Generate this automatically from github
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
console.writeln("\x1B\x06Mayhem:\x1B\x10");
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Mayhem:");
console.writeln("eried,euquiq,gregoryfenton");
console.writeln("johnelder,jwetzell,nnemanjan00");
console.writeln("N0vaPixel,klockee,GullCode");
@@ -33,12 +33,17 @@ void AboutView::update() {
console.writeln("heurist1,intoxsick,ckuethe");
console.writeln("notpike,jLynx,zigad");
console.writeln("MichalLeonBorsuk,jimilinuxguy");
console.writeln("kallanreed,bernd-herzog");
break;
case 2:
console.writeln("NotherNgineer,zxkmm,u-foka");
console.writeln("Netro,HTotoo");
console.writeln("");
break;
case 2:
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
console.writeln("\x1B\x06Havoc:\x1B\x10");
case 3:
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Havoc:");
console.writeln("furrtek,mrmookie,NotPike");
console.writeln("mjwaxios,ImDroided,Giorgiofox");
console.writeln("F4GEV,z4ziggy,xmycroftx");
@@ -49,16 +54,16 @@ void AboutView::update() {
console.writeln("");
break;
case 3:
// https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
console.writeln("\x1B\x06PortaPack:\x1B\x10");
case 4:
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "PortaPack:");
console.writeln("jboone,argilo");
console.writeln("");
break;
case 4:
case 5:
// https://github.com/mossmann/hackrf/graphs/contributors
console.writeln("\x1B\x06HackRF:\x1B\x10");
console.writeln(STR_COLOR_DARK_YELLOW "HackRF:");
console.writeln("mossmann,dominicgs,bvernoux");
console.writeln("bgamari,schneider42,miek");
console.writeln("willcode,hessu,Sec42");
@@ -3,7 +3,6 @@
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include <cstdint>
+388 -356
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -20,46 +21,50 @@
* Boston, MA 02110-1301, USA.
*/
#include <strings.h>
#include <algorithm>
#include "ui_adsb_rx.hpp"
#include "ui_alphanum.hpp"
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "rtc_time.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace ui {
static std::string get_map_tag(const AircraftRecentEntry& entry) {
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
}
template <>
void RecentEntriesTable<AircraftRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
char aged_color;
Color target_color;
auto entry_age = entry.age;
std::string entry_string;
// Color decay for flights not being updated anymore
if (entry_age < ADSB_CURRENT) {
aged_color = 0x10;
target_color = Color::green();
} else if (entry_age < ADSB_RECENT) {
aged_color = 0x07;
target_color = Color::light_grey();
} else {
aged_color = 0x08;
target_color = Color::dark_grey();
}
switch (entry.state) {
case ADSBAgeState::Invalid:
case ADSBAgeState::Current:
entry_string = "";
target_color = Color::green();
break;
case ADSBAgeState::Recent:
entry_string = STR_COLOR_LIGHT_GREY;
target_color = Color::light_grey();
break;
default:
entry_string = STR_COLOR_DARK_GREY;
target_color = Color::grey();
};
std::string entry_string = "\x1B";
entry_string += aged_color;
entry_string +=
(entry.callsign[0] != ' ' ? entry.callsign + " " : entry.icaoStr + " ") +
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4) +
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4) + " " +
@@ -72,47 +77,59 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
entry_string);
if (entry.pos.valid)
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0), bitmap_target, target_color, style.background);
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
bitmap_target, target_color, style.background);
}
void ADSBLogger::log_str(std::string& logline) {
log_file.write_entry(logline);
/* ADSBLogger ********************************************/
void ADSBLogger::log(const ADSBLogEntry& log_entry) {
std::string log_line;
log_line.reserve(100);
log_line = log_entry.raw_data;
log_line += "ICAO:" + log_entry.icao;
if (!log_entry.callsign.empty())
log_line += " " + log_entry.callsign;
if (log_entry.pos.valid)
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude) +
" Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(log_entry.pos.longitude, 7);
if (log_entry.vel.valid)
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
" Spd: " + to_string_dec_int(log_entry.vel.speed);
log_file.write_entry(log_line);
}
// Aircraft Details
void ADSBRxAircraftDetailsView::focus() {
button_close.focus();
}
ADSBRxAircraftDetailsView::~ADSBRxAircraftDetailsView() {
on_close_();
}
/* ADSBRxAircraftDetailsView *****************************/
ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
NavigationView& nav,
const AircraftRecentEntry& entry,
const std::function<void(void)> on_close)
: entry_copy(entry),
on_close_(on_close) {
add_children({&labels,
&text_icao_address,
&text_registration,
&text_manufacturer,
&text_model,
&text_type,
&text_number_of_engines,
&text_engine_type,
&text_owner,
&text_operator,
&button_close});
const AircraftRecentEntry& entry) {
add_children(
{&labels,
&text_icao_address,
&text_registration,
&text_manufacturer,
&text_model,
&text_type,
&text_number_of_engines,
&text_engine_type,
&text_owner,
&text_operator,
&button_close});
std::unique_ptr<ADSBLogger> logger{};
icao_code = to_string_hex(entry_copy.ICAO_address, 6);
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
text_icao_address.set(entry.icao_str);
// Try getting the aircraft information from icao24.db
return_code = db.retrieve_aircraft_record(&aircraft_record, icao_code);
std::database db{};
std::database::AircraftDBRecord aircraft_record;
auto return_code = db.retrieve_aircraft_record(&aircraft_record, entry.icao_str);
switch (return_code) {
case DATABASE_RECORD_FOUND:
text_registration.set(aircraft_record.aircraft_registration);
@@ -120,6 +137,7 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_model.set(aircraft_record.aircraft_model);
text_owner.set(aircraft_record.aircraft_owner);
text_operator.set(aircraft_record.aircraft_operator);
// Check for ICAO type, e.g. L2J
if (strlen(aircraft_record.icao_type) == 3) {
switch (aircraft_record.icao_type[0]) {
@@ -142,7 +160,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_type.set("Tilt-wing aircraft");
break;
}
text_number_of_engines.set(std::string(1, aircraft_record.icao_type[1]));
text_number_of_engines.set(std::string{1, aircraft_record.icao_type[1]});
switch (aircraft_record.icao_type[2]) {
case 'P':
text_engine_type.set("Piston engine");
@@ -157,8 +176,8 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_engine_type.set("Electric engine");
break;
}
}
// Check for ICAO type designator
else if (strlen(aircraft_record.icao_type) == 4) {
if (strcmp(aircraft_record.icao_type, "SHIP") == 0)
@@ -179,75 +198,49 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_type.set("Powered parachute/paraplane");
}
break;
case DATABASE_NOT_FOUND:
text_manufacturer.set("No icao24.db file");
break;
}
button_close.on_select = [&nav](Button&) {
nav.pop();
};
};
// End of Aicraft details
void ADSBRxDetailsView::focus() {
button_see_map.focus();
}
void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
entry_copy = entry;
uint32_t age = entry_copy.age;
if (age < 60)
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
else
text_last_seen.set(to_string_dec_uint(age / 60) + " minutes ago");
text_infos.set(entry_copy.info_string);
if (entry_copy.velo.heading < 360 && entry_copy.velo.speed >= 0) { // I don't like this but...
text_info2.set("Hdg:" + to_string_dec_uint(entry_copy.velo.heading) + " Spd:" + to_string_dec_int(entry_copy.velo.speed));
} else {
text_info2.set("");
}
text_frame_pos_even.set(to_string_hex_array(entry_copy.frame_pos_even.get_raw_data(), 14));
text_frame_pos_odd.set(to_string_hex_array(entry_copy.frame_pos_odd.get_raw_data(), 14));
if (send_updates) {
geomap_view->update_tag(trimr(entry.callsign[0] != ' ' ? entry.callsign : to_string_hex(entry.ICAO_address, 6)));
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, entry_copy.velo.heading, entry_copy.pos.altitude);
}
void ADSBRxAircraftDetailsView::focus() {
button_close.focus();
}
ADSBRxDetailsView::~ADSBRxDetailsView() {
on_close_();
}
/* ADSBRxDetailsView *************************************/
ADSBRxDetailsView::ADSBRxDetailsView(
NavigationView& nav,
const AircraftRecentEntry& entry,
const std::function<void(void)> on_close)
: entry_copy(entry),
on_close_(on_close) {
add_children({&labels,
&text_icao_address,
&text_callsign,
&text_last_seen,
&text_airline,
&text_country,
&text_infos,
&text_info2,
&text_frame_pos_even,
&text_frame_pos_odd,
&button_aircraft_details,
&button_see_map});
const AircraftRecentEntry& entry)
: entry_(entry) {
add_children(
{&labels,
&text_icao_address,
&text_callsign,
&text_last_seen,
&text_airline,
&text_country,
&text_infos,
&text_info2,
&text_frame_pos_even,
&text_frame_pos_odd,
&button_aircraft_details,
&button_see_map});
std::unique_ptr<ADSBLogger> logger{};
update(entry_copy);
// The following won't change for a given airborne aircraft.
// Try getting the airline's name from airlines.db.
// NB: Only works once callsign has been read and won't be updated.
std::database db;
std::database::AirlinesDBRecord airline_record;
std::string airline_code = entry_.callsign.substr(0, 3);
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
// The following won't (shouldn't !) change for a given airborne aircraft
// Try getting the airline's name from airlines.db
airline_code = entry_copy.callsign.substr(0, 3);
return_code = db.retrieve_airline_record(&airline_record, airline_code);
switch (return_code) {
case DATABASE_RECORD_FOUND:
text_airline.set(airline_record.airline);
@@ -258,285 +251,324 @@ ADSBRxDetailsView::ADSBRxDetailsView(
break;
}
text_callsign.set(entry_copy.callsign);
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
text_icao_address.set(entry_.icao_str);
button_aircraft_details.on_select = [this, &nav](Button&) {
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(entry_copy, [this]() { send_updates = false; });
send_updates = false;
aircraft_details_view_ = nav.push<ADSBRxAircraftDetailsView>(entry_);
nav.set_on_pop([this]() {
aircraft_details_view_ = nullptr;
refresh_ui();
});
};
button_see_map.on_select = [this, &nav](Button&) {
if (!send_updates) { // Prevent recursively launching the map
geomap_view = nav.push<GeoMapView>(
trimr(entry_copy.callsign[0] != ' ' ? entry_copy.callsign : entry_copy.icaoStr),
entry_copy.pos.altitude,
GeoPos::alt_unit::FEET,
entry_copy.pos.latitude,
entry_copy.pos.longitude,
entry_copy.velo.heading,
[this]() {
send_updates = false;
});
send_updates = true;
}
geomap_view_ = nav.push<GeoMapView>(
get_map_tag(entry_),
entry_.pos.altitude,
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::MPH,
entry_.pos.latitude,
entry_.pos.longitude,
entry_.velo.heading);
nav.set_on_pop([this]() {
geomap_view_ = nullptr;
refresh_ui();
});
};
refresh_ui();
};
void ADSBRxView::focus() {
field_vga.focus();
void ADSBRxDetailsView::focus() {
button_see_map.focus();
}
ADSBRxView::~ADSBRxView() {
receiver_model.set_tuning_frequency(prevFreq); // Restore previous frequency on exit
void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
entry_ = entry;
// save app settings
settings.save("rx_adsb", &app_settings);
// TODO: once all apps keep there own settin previous frequency logic can be removed
receiver_model.set_tuning_frequency(prevFreq);
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
}
AircraftRecentEntry ADSBRxView::find_or_create_entry(uint32_t ICAO_address) {
auto it = find(recent, ICAO_address);
// If not found
if (it == std::end(recent)) {
recent.emplace_front(ICAO_address); // Add it
it = find(recent, ICAO_address); // Find it again
}
return *it;
}
void ADSBRxView::replace_entry(AircraftRecentEntry& entry) {
uint32_t ICAO_address = entry.ICAO_address;
std::replace_if(
recent.begin(), recent.end(),
[ICAO_address](const AircraftRecentEntry& compEntry) { return ICAO_address == compEntry.ICAO_address; },
entry);
}
void ADSBRxView::remove_old_entries() {
auto it = recent.rbegin();
auto end = recent.rend();
while (it != end) {
if (it->age_state >= 4) {
std::advance(it, 1);
recent.erase(it.base());
} else {
break; // stop looking because the list is sorted
}
}
}
void ADSBRxView::sort_entries_by_state() {
// Sorting List pn age_state using lambda function as comparator
recent.sort([](const AircraftRecentEntry& left, const AircraftRecentEntry& right) { return (left.age_state < right.age_state); });
}
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
logger = std::make_unique<ADSBLogger>();
rtc::RTC datetime;
std::string callsign;
std::string str_info;
std::string logentry;
auto frame = message->frame;
uint32_t ICAO_address = frame.get_ICAO_address();
if (frame.check_CRC() && ICAO_address) {
rtcGetTime(&RTCD1, &datetime);
auto entry = find_or_create_entry(ICAO_address);
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
entry.reset_age();
if (entry.hits == 0) {
entry.amp = message->amp; // Store amplitude on first hit
} else {
entry.amp = ((entry.amp * 15) + message->amp) >> 4; // Update smoothed amplitude on updates
}
entry.inc_hit();
if (logger) {
logentry += to_string_hex_array(frame.get_raw_data(), 14) + " ";
logentry += "ICAO:" + entry.icaoStr + " ";
}
if (frame.get_DF() == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
uint8_t msg_sub = frame.get_msg_sub();
uint8_t* raw_data = frame.get_raw_data();
// 4: // surveillance, altitude reply
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
callsign = decode_frame_id(frame);
entry.set_callsign(callsign);
if (logger) {
logentry += callsign + " ";
}
}
// 9:
// 18: { // Extended squitter/non-transponder
// 21: // Comm-B, identity reply
// 20: // Comm-B, altitude reply
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
entry.set_frame_pos(frame, raw_data[6] & 4);
if (entry.pos.valid) {
str_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(str_info);
if (logger) {
// printing the coordinates in the log file with more
// resolution, as we are not constrained by screen
// real estate there:
std::string log_info = "Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(entry.pos.longitude, 7);
logentry += log_info + " ";
}
}
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
entry.set_frame_velo(frame);
if (logger) {
logger->append(LOG_ROOT_DIR "/ADSB.TXT");
logentry += "Type:" + to_string_dec_uint(msg_sub) +
" Hdg:" + to_string_dec_uint(entry.velo.heading) +
" Spd: " + to_string_dec_int(entry.velo.speed);
}
}
replace_entry(entry);
} // frame.get_DF() == DF_ADSB
if (logger) {
logger->append(LOG_ROOT_DIR "/ADSB.TXT");
// will log each frame in format:
// 20171103100227 8DADBEEFDEADBEEFDEADBEEFDEADBEEF ICAO:nnnnnn callsign Alt:nnnnnn Latnnn.nn Lonnnn.nn
logger->log_str(logentry);
}
}
}
void ADSBRxView::on_tick_second() {
if (recent.size() <= 16) { // Not many entries update everything (16 is one screen full)
updateDetailsAndMap(1);
updateRecentEntries();
} else if (updateState == 0) { // Even second
updateState = 1;
updateDetailsAndMap(2);
} else { // Odd second only performed when there are many entries
updateState = 0;
updateRecentEntries();
}
}
void ADSBRxView::updateDetailsAndMap(int ageStep) {
ui::GeoMarker marker;
bool storeNewMarkers = false;
// Sort and truncate the entries, grouped, newest group first
sort_entries_by_state();
truncate_entries(recent);
remove_old_entries();
// Calculate if it is time to update markers
if (send_updates && details_view && details_view->geomap_view) {
ticksSinceMarkerRefresh += ageStep;
if (ticksSinceMarkerRefresh >= MARKER_UPDATE_SECONDS) { // Update other aircraft every few seconds
storeNewMarkers = true;
ticksSinceMarkerRefresh = 0;
}
if (aircraft_details_view_) {
// AC Details view is showing, nothing to update.
} else if (geomap_view_) {
// Map is showing, update the current item.
geomap_view_->update_tag(get_map_tag(entry_));
geomap_view_->update_position(entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude, entry.velo.speed);
} else {
ticksSinceMarkerRefresh = MARKER_UPDATE_SECONDS; // Send the markers as soon as the geoview exists
}
// Increment age, and pass updates to the details and map
const bool otherMarkersCanBeSent = send_updates && storeNewMarkers && details_view && details_view->geomap_view; // Save retesting all of this
MapMarkerStored markerStored = MARKER_NOT_STORED;
if (otherMarkersCanBeSent) {
details_view->geomap_view->clear_markers();
}
// Loop through all entries
for (auto& entry : recent) {
entry.inc_age(ageStep);
// Only if there is a details view
if (send_updates && details_view) {
if (entry.key() == detailed_entry_key) // Check if the ICAO address match
{
details_view->update(entry);
}
// Store if the view is present and the list isn't full
else if (otherMarkersCanBeSent && (markerStored != MARKER_LIST_FULL) && entry.pos.valid && (entry.age_state <= 2)) {
marker.lon = entry.pos.longitude;
marker.lat = entry.pos.latitude;
marker.angle = entry.velo.heading;
marker.tag = trimr(entry.callsign[0] != ' ' ? entry.callsign : entry.icaoStr);
markerStored = details_view->geomap_view->store_marker(marker);
}
}
} // Loop through all entries, if only to update the age
}
void ADSBRxView::updateRecentEntries() {
// Redraw the list of aircraft
if (!send_updates) {
recent_entries_view.set_dirty();
// Details is showing, update details.
refresh_ui();
}
}
void ADSBRxDetailsView::clear_map_markers() {
if (geomap_view_)
geomap_view_->clear_markers();
}
bool ADSBRxDetailsView::add_map_marker(const AircraftRecentEntry& entry) {
// Map not shown, can't add markers.
if (!geomap_view_)
return false;
GeoMarker marker{};
marker.lon = entry.pos.longitude;
marker.lat = entry.pos.latitude;
marker.angle = entry.velo.heading;
marker.tag = get_map_tag(entry);
auto markerStored = geomap_view_->store_marker(marker);
return markerStored == MARKER_STORED;
}
void ADSBRxDetailsView::on_gps(const GPSPosDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
}
void ADSBRxDetailsView::on_orientation(const OrientationDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_orientation(msg->angle);
}
void ADSBRxDetailsView::refresh_ui() {
auto age = entry_.age;
if (age < 60)
text_last_seen.set(to_string_dec_uint(age) + " seconds ago");
else
text_last_seen.set(to_string_dec_uint(age / 60) + " minutes ago");
text_callsign.set(entry_.callsign);
text_infos.set(entry_.info_string);
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
" Spd:" + to_string_dec_int(entry_.velo.speed));
else
text_info2.set("");
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
}
/* ADSBRxView ********************************************/
ADSBRxView::ADSBRxView(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_adsb_rx);
add_children({&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&rssi,
&recent_entries_view});
// load app settings
auto rc = settings.load("rx_adsb", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
} else {
field_lna.set_value(40);
field_vga.set_value(40);
}
add_children(
{&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&rssi,
&recent_entries_view,
&status_frame,
&status_good_frame});
recent_entries_view.set_parent_rect({0, 16, 240, 272});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detailed_entry_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(
entry,
[this]() {
send_updates = false;
});
send_updates = true;
detail_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(entry);
nav.set_on_pop([this]() {
detail_key = AircraftRecentEntry::invalid_key;
details_view = nullptr;
});
};
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
prevFreq = receiver_model.tuning_frequency(); // Store previous frequency on creation
logger = std::make_unique<ADSBLogger>();
logger->append(LOG_ROOT_DIR "/ADSB.TXT");
baseband::set_adsb();
receiver_model.set_tuning_frequency(1090000000);
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(2000000);
receiver_model.set_baseband_bandwidth(2500000);
receiver_model.enable();
baseband::set_adsb();
}
ADSBRxView::~ADSBRxView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
}
void ADSBRxView::focus() {
field_vga.focus();
}
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
auto frame = message->frame;
uint32_t ICAO_address = frame.get_ICAO_address();
status_frame.toggle();
// Bad frame, skip it.
if (!frame.check_CRC() || ICAO_address == 0)
return;
ADSBLogEntry log_entry;
status_good_frame.toggle();
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime); // Reading RTC directly to avoid DST transitions when calculating delta
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
// NB: Reference to update entry in-place.
auto& entry = find_or_create_entry(ICAO_address);
entry.inc_hit();
entry.reset_age();
// Store smoothed amplitude on updates.
entry.amp = entry.hits == 0
? message->amp
: ((entry.amp * 15) + message->amp) >> 4;
log_entry.raw_data = to_string_hex_array(frame.get_raw_data(), 14);
log_entry.icao = entry.icao_str;
if (frame.get_DF() == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
uint8_t msg_sub = frame.get_msg_sub();
uint8_t* raw_data = frame.get_raw_data();
// 4: // surveillance, altitude reply
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
entry.set_callsign(decode_frame_id(frame));
log_entry.callsign = entry.callsign;
}
// 9:
// 18: // Extended squitter/non-transponder
// 21: // Comm-B, identity reply
// 20: // Comm-B, altitude reply
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
entry.set_frame_pos(frame, raw_data[6] & 4);
log_entry.pos = entry.pos;
if (entry.pos.valid) {
std::string str_info =
"Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(std::move(str_info));
}
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
entry.set_frame_velo(frame);
log_entry.vel = entry.velo;
log_entry.vel_type = msg_sub;
}
}
logger->log(log_entry);
}
void ADSBRxView::on_tick_second() {
status_frame.reset();
status_good_frame.reset();
++tick_count;
++ticks_since_marker_refresh;
// Small list, update all at once.
if (recent.size() <= max_update_entries) {
update_recent_entries(/*age_delta*/ 1);
refresh_ui();
return;
}
// Too many items, split update work into two phases:
// Entry maintenance and UI update.
if ((tick_count & 1) == 0)
update_recent_entries(/*age_delta*/ 2);
else
refresh_ui();
}
void ADSBRxView::refresh_ui() {
// There's only one ticks handler, but 3 UIs that need to be updated.
// This code will dispatch updates to the currently active view.
if (details_view) {
// The details view is showing, forward updates to that UI.
bool current_updated = false;
bool map_needs_update = false;
if (details_view->map_active()) {
// Is it time to clear and refresh the map's markers?
if (ticks_since_marker_refresh >= MARKER_UPDATE_SECONDS) {
map_needs_update = true;
ticks_since_marker_refresh = 0;
details_view->clear_map_markers();
}
} else {
// Refresh map immediately once active.
ticks_since_marker_refresh = MARKER_UPDATE_SECONDS;
}
// Process the entries list.
for (const auto& entry : recent) {
// Found the entry being shown in details view. Update it.
if (entry.key() == detail_key) {
details_view->update(entry);
current_updated = true;
}
// NB: current entry also gets a marker so it shows up if map is panned.
if (map_needs_update && entry.pos.valid && entry.state <= ADSBAgeState::Recent) {
map_needs_update = details_view->add_map_marker(entry);
}
// Any work left to do?
if (current_updated && !map_needs_update)
break;
}
} else {
// Main page is the top view. Redraw the entries view.
recent_entries_view.set_dirty();
}
}
void ADSBRxView::update_recent_entries(int age_delta) {
for (auto& entry : recent)
entry.inc_age(age_delta);
// Sort and truncate the entries, grouped by state, newest first.
sort_entries_by_state();
truncate_entries(recent);
remove_expired_entries();
}
AircraftRecentEntry& ADSBRxView::find_or_create_entry(uint32_t ICAO_address) {
// Find ...
auto it = find(recent, ICAO_address);
if (it != recent.end())
return *it;
// ... or Create.
return recent.emplace_front(ICAO_address);
}
void ADSBRxView::sort_entries_by_state() {
recent.sort([](const auto& left, const auto& right) {
return (left.state < right.state);
});
}
void ADSBRxView::remove_expired_entries() {
// NB: Assumes entried are sorted with oldest last.
auto it = recent.rbegin();
auto end = recent.rend();
// Find the first !expired entry from the back.
while (it != end) {
if (it->state != ADSBAgeState::Expired)
break;
std::advance(it, 1);
}
// Remove the range of expired items.
recent.erase(it.base(), recent.end());
}
} /* namespace ui */
+151 -104
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -21,29 +22,23 @@
*/
#include "ui.hpp"
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "database.hpp"
#include "recent_entries.hpp"
#include "log_file.hpp"
#include "adsb.hpp"
#include "message.hpp"
#include "app_settings.hpp"
#include "crc.hpp"
#include "database.hpp"
#include "file.hpp"
#include "log_file.hpp"
#include "message.hpp"
#include "radio_state.hpp"
#include "recent_entries.hpp"
#include "string_format.hpp"
using namespace adsb;
namespace ui {
#define ADSB_CURRENT 10 // Seconds
#define ADSB_RECENT 30 // Seconds
#define ADSB_REMOVE 300 // Used for removing old entries
#define AIRCRAFT_ID_L 1 // aircraft ID message type (lowest type id)
#define AIRCRAFT_ID_H 4 // aircraft ID message type (highest type id)
@@ -66,8 +61,26 @@ namespace ui {
#define VEL_AIR_SUBSONIC 3
#define VEL_AIR_SUPERSONIC 4
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define MARKER_UPDATE_SECONDS 5 // "other" map marker redraw interval
/* Thresholds (in seconds) that define the transition between ages. */
struct ADSBAgeLimit {
static constexpr int Current = 10;
static constexpr int Recent = 30;
static constexpr int Expired = 300;
};
/* Age states used for sorting and drawing recent entries. */
enum class ADSBAgeState : uint8_t {
Invalid,
Current,
Recent,
Old,
Expired,
};
/* Data extracted from ADSB frames. */
struct AircraftRecentEntry {
using Key = uint32_t;
@@ -76,30 +89,30 @@ struct AircraftRecentEntry {
uint32_t ICAO_address{};
uint16_t hits{0};
uint16_t age_state{1};
uint32_t age{0};
ADSBAgeState state{ADSBAgeState::Invalid};
uint32_t age{0}; // In seconds
uint32_t amp{0};
adsb_pos pos{false, 0, 0, 0};
adsb_vel velo{false, 0, 999, 0};
ADSBFrame frame_pos_even{};
ADSBFrame frame_pos_odd{};
std::string icaoStr{" "};
std::string callsign{" "};
std::string info_string{""};
std::string icao_str{};
std::string callsign{};
std::string info_string{};
AircraftRecentEntry(
const uint32_t ICAO_address)
AircraftRecentEntry(const uint32_t ICAO_address)
: ICAO_address{ICAO_address} {
this->icaoStr = to_string_hex(ICAO_address, 6);
this->icao_str = to_string_hex(ICAO_address, 6);
}
/* RecentEntries helpers expect a "key" on every item. */
Key key() const {
return ICAO_address;
}
void set_callsign(std::string& new_callsign) {
callsign = new_callsign;
void set_callsign(std::string new_callsign) {
callsign = std::move(new_callsign);
}
void inc_hit() {
@@ -122,8 +135,8 @@ struct AircraftRecentEntry {
velo = decode_frame_velo(frame);
}
void set_info_string(std::string& new_info_string) {
info_string = new_info_string;
void set_info_string(std::string new_info_string) {
info_string = std::move(new_info_string);
}
void reset_age() {
@@ -132,59 +145,59 @@ struct AircraftRecentEntry {
void inc_age(int delta) {
age += delta;
if (age < ADSB_CURRENT) {
age_state = pos.valid ? 0 : 1;
} else if (age < ADSB_RECENT) {
age_state = 2;
} else if (age < ADSB_REMOVE) {
age_state = 3;
} else {
age_state = 4;
}
if (age < ADSBAgeLimit::Current)
state = pos.valid ? ADSBAgeState::Invalid
: ADSBAgeState::Current;
else if (age < ADSBAgeLimit::Recent)
state = ADSBAgeState::Recent;
else if (age < ADSBAgeLimit::Expired)
state = ADSBAgeState::Old;
else
state = ADSBAgeState::Expired;
}
};
// NB: uses std::list underneath so assuming refs are NOT invalidated.
using AircraftRecentEntries = RecentEntries<AircraftRecentEntry>;
/* Holds data for logging. */
struct ADSBLogEntry {
std::string raw_data{};
std::string icao{};
std::string callsign{};
adsb_pos pos{};
adsb_vel vel{};
uint8_t vel_type{};
};
// TODO: Make logging optional.
/* Logs entries to a log file. */
class ADSBLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_str(std::string& logline);
void log(const ADSBLogEntry& log_entry);
private:
LogFile log_file{};
};
/* Shows detailed information about an aircraft. */
class ADSBRxAircraftDetailsView : public View {
public:
ADSBRxAircraftDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
~ADSBRxAircraftDetailsView();
ADSBRxAircraftDetailsView(const ADSBRxAircraftDetailsView&) = delete;
ADSBRxAircraftDetailsView(ADSBRxAircraftDetailsView&&) = delete;
ADSBRxAircraftDetailsView& operator=(const ADSBRxAircraftDetailsView&) = delete;
ADSBRxAircraftDetailsView& operator=(ADSBRxAircraftDetailsView&&) = delete;
ADSBRxAircraftDetailsView(
NavigationView&,
const AircraftRecentEntry& entry);
void focus() override;
void update(const AircraftRecentEntry& entry);
std::string title() const override { return "AC Details"; };
AircraftRecentEntry get_current_entry() { return entry_copy; }
std::database::AircraftDBRecord aircraft_record = {};
std::string title() const override { return "AC Details"; }
private:
AircraftRecentEntry entry_copy{0};
std::function<void(void)> on_close_{};
bool send_updates{false};
std::database db = {};
std::string icao_code = "";
int return_code = 0;
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
{{0 * 8, 2 * 16}, "Registration:", Color::light_grey()},
@@ -237,36 +250,36 @@ class ADSBRxAircraftDetailsView : public View {
"Back"};
};
/* Shows detailed information about an aircraft's flight. */
class ADSBRxDetailsView : public View {
public:
ADSBRxDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function<void(void)> on_close);
~ADSBRxDetailsView();
ADSBRxDetailsView(NavigationView&, const AircraftRecentEntry& entry);
ADSBRxDetailsView(const ADSBRxDetailsView&) = delete;
ADSBRxDetailsView(ADSBRxDetailsView&&) = delete;
ADSBRxDetailsView& operator=(const ADSBRxDetailsView&) = delete;
ADSBRxDetailsView& operator=(ADSBRxDetailsView&&) = delete;
void focus() override;
void update(const AircraftRecentEntry& entry);
std::string title() const override { return "Details"; };
/* Calls forwarded to map view if shown. */
bool map_active() const { return geomap_view_; }
void clear_map_markers();
/* Adds a marker for the entry to the map. Returns true on success. */
bool add_map_marker(const AircraftRecentEntry& entry);
AircraftRecentEntry get_current_entry() { return entry_copy; }
std::database::AirlinesDBRecord airline_record = {};
GeoMapView* geomap_view{nullptr};
std::string title() const override { return "Details"; }
private:
AircraftRecentEntry entry_copy{0};
std::function<void(void)> on_close_{};
ADSBRxAircraftDetailsView* aircraft_details_view{nullptr};
bool send_updates{false};
std::database db = {};
std::string airline_code = "";
int return_code = 0;
void refresh_ui();
void on_gps(const GPSPosDataMessage* msg);
void on_orientation(const OrientationDataMessage* msg);
GeoMapView* geomap_view_{nullptr};
ADSBRxAircraftDetailsView* aircraft_details_view_{nullptr};
// NB: Keeping a copy so that it doesn't end up dangling
// if removed from the recent entries list.
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
@@ -319,8 +332,22 @@ class ADSBRxDetailsView : public View {
Button button_see_map{
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
"See on map"};
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
[this](Message* const p) {
const auto message = static_cast<const GPSPosDataMessage*>(p);
this->on_gps(message);
}};
MessageHandlerRegistration message_handler_orientation{
Message::ID::OrientationData,
[this](Message* const p) {
const auto message = static_cast<const OrientationDataMessage*>(p);
this->on_orientation(message);
}};
};
/* Main ADSB application view and message dispatch. */
class ADSBRxView : public View {
public:
ADSBRxView(NavigationView& nav);
@@ -332,42 +359,52 @@ class ADSBRxView : public View {
ADSBRxView& operator=(ADSBRxView&&) = delete;
void focus() override;
std::string title() const override { return "ADS-B RX"; };
void replace_entry(AircraftRecentEntry& entry);
void remove_old_entries();
AircraftRecentEntry find_or_create_entry(uint32_t ICAO_address);
void sort_entries_by_state();
private:
rf::Frequency prevFreq = {0};
RxRadioState radio_state_{
1'090'000'000 /* frequency */,
2'500'000 /* bandwidth */,
2'000'000 /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
app_settings::SettingsManager settings_{
"rx_adsb", app_settings::Mode::RX};
std::unique_ptr<ADSBLogger> logger{};
/* Event Handlers */
void on_frame(const ADSBFrameMessage* message);
void on_tick_second();
int updateState = {0};
void updateRecentEntries();
void updateDetailsAndMap(int ageStep);
#define MARKER_UPDATE_SECONDS (5)
int ticksSinceMarkerRefresh{MARKER_UPDATE_SECONDS - 1};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
const RecentEntriesColumns columns{{{"ICAO/Call", 9},
{"Lvl", 3},
{"Spd", 3},
{"Amp", 3},
{"Hit", 3},
{"Age", 4}}};
AircraftRecentEntries recent{};
RecentEntriesView<RecentEntries<AircraftRecentEntry>> recent_entries_view{columns, recent};
void refresh_ui();
SignalToken signal_token_tick_second{};
uint8_t tick_count = 0;
uint16_t ticks_since_marker_refresh{MARKER_UPDATE_SECONDS};
/* Max number of entries that can be updated in a single pass.
* 16 is one screen of recent entries. */
static constexpr uint8_t max_update_entries = 16;
/* Recent Entries */
const RecentEntriesColumns columns{
{{"ICAO/Call", 9},
{"Lvl", 3},
{"Spd", 3},
{"Amp", 3},
{"Hit", 3},
{"Age", 4}}};
AircraftRecentEntries recent{};
RecentEntriesView<AircraftRecentEntries> recent_entries_view{columns, recent};
/* Entry Management */
void update_recent_entries(int age_delta);
AircraftRecentEntry& find_or_create_entry(uint32_t ICAO_address);
void sort_entries_by_state();
void remove_expired_entries();
/* The key of the entry in the details view if shown. */
AircraftRecentEntry::Key detail_key{AircraftRecentEntry::invalid_key};
ADSBRxDetailsView* details_view{nullptr};
uint32_t detailed_entry_key{0};
bool send_updates{false};
Labels labels{
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Color::light_grey()}};
@@ -382,7 +419,17 @@ class ADSBRxView : public View {
{18 * 8, 0 * 16}};
RSSI rssi{
{20 * 8, 4, 10 * 8, 8},
{20 * 8, 4, 10 * 7, 8},
};
ActivityDot status_frame{
{screen_width - 3, 5, 2, 2},
Color::white(),
};
ActivityDot status_good_frame{
{screen_width - 3, 9, 2, 2},
Color::green(),
};
MessageHandlerRegistration message_handler_frame{
+12 -24
View File
@@ -26,7 +26,6 @@
#include "manchester.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "cpld_update.hpp"
#include "baseband_api.hpp"
#include <cstring>
@@ -86,10 +85,12 @@ ADSBPositionView::ADSBPositionView(
nav.push<GeoMapView>(
geopos.altitude(),
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::HIDDEN,
geopos.lat(),
geopos.lon(),
[this](int32_t altitude, float lat, float lon) {
[this](int32_t altitude, float lat, float lon, int32_t speed) {
geopos.set_altitude(altitude);
geopos.set_speed(speed);
geopos.set_lat(lat);
geopos.set_lon(lon);
});
@@ -187,13 +188,12 @@ ADSBSquawkView::ADSBSquawkView(
&field_squawk});
}
void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
void ADSBSquawkView::collect_frames(const uint32_t /*ICAO_address*/, std::vector<ADSBFrame>& frame_list) {
if (!enabled) return;
ADSBFrame temp_frame;
(void)ICAO_address;
encode_frame_squawk(temp_frame, field_squawk.concatenate_4_octal_u16());
encode_frame_squawk(temp_frame, field_squawk.to_integer());
frame_list.emplace_back(temp_frame);
}
@@ -273,17 +273,12 @@ void ADSBTxView::focus() {
}
ADSBTxView::~ADSBTxView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_adsb", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, withouth ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void ADSBTxView::generate_frames() {
const uint32_t ICAO_address = sym_icao.value_hex_u64();
const uint32_t ICAO_address = sym_icao.to_integer();
frames.clear();
@@ -303,8 +298,9 @@ void ADSBTxView::generate_frames() {
void ADSBTxView::start_tx() {
generate_frames();
transmitter_model.set_sampling_rate(4000000U);
transmitter_model.set_baseband_bandwidth(10000000);
/* Already tested , with SDR Angel + another Hackrf RX and dump1090 + SDR RLT. Final conclusion is TX LPF 6 Mhz is
* the best settings to fulfill ADSB transponder spectrum mask requirements (<=-20 dB's at +-7Mhz , <=-40 dB's at +-23Mhz )
* and not showing any ADSB data decoding degradation.*/
transmitter_model.enable();
baseband::set_adsb();
@@ -327,18 +323,10 @@ ADSBTxView::ADSBTxView(
&text_frame,
&tx_view});
// load app settings
auto rc = settings.load("tx_adsb", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+12 -6
View File
@@ -29,6 +29,7 @@
#include "message.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
using namespace adsb;
@@ -57,7 +58,8 @@ class ADSBPositionView : public OptionTabView {
private:
GeoPos geopos{
{0, 2 * 16},
GeoPos::FEET};
GeoPos::FEET,
GeoPos::HIDDEN};
Button button_set_map{
{8 * 8, 6 * 16, 14 * 8, 2 * 16},
@@ -134,7 +136,7 @@ class ADSBSquawkView : public OptionTabView {
SymField field_squawk{
{10 * 8, 2 * 16},
4,
SymField::SYMFIELD_OCT};
SymField::Type::Oct};
};
class ADSBTXThread {
@@ -201,9 +203,13 @@ class ADSBTxView : public View {
-1
};*/
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
1090000000 /* frequency */,
6000000 /* bandwidth */,
4000000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_adsb", app_settings::Mode::TX};
// tx_modes tx_mode = IDLE;
NavigationView& nav_;
@@ -231,7 +237,7 @@ class ADSBTxView : public View {
SymField sym_icao{
{10 * 8, 4 * 8},
6,
SymField::SYMFIELD_HEX};
SymField::Type::Hex};
Text text_frame{
{1 * 8, 29 * 8, 14 * 8, 16},
+6 -37
View File
@@ -42,15 +42,12 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
const Rect& target_rect,
Painter& painter,
const Style& style) {
char aged_color;
Color target_color;
// auto entry_age = entry.age;
target_color = Color::green();
aged_color = 0x10;
std::string entry_string = "\x1B";
entry_string += aged_color;
std::string entry_string = "";
entry_string += entry.source_formatted;
entry_string.append(10 - entry.source_formatted.size(), ' ');
@@ -73,12 +70,8 @@ void APRSRxView::focus() {
options_region.focus();
}
void APRSRxView::update_freq(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
: View(parent_rect) {
: View(parent_rect), nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_aprs_rx);
add_children({&rssi,
@@ -86,19 +79,12 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&options_region,
&field_frequency,
&record_view,
&console});
// load app settings
auto rc = settings.load("rx_aprs", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
// DEBUG
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
@@ -118,19 +104,7 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
}
};
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(100);
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
options_region.set_selected_index(0, true);
logger = std::make_unique<APRSLogger>();
@@ -142,9 +116,6 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.enable();
}
@@ -210,9 +181,6 @@ void APRSRxView::on_show() {
}
APRSRxView::~APRSRxView() {
// save app settings
settings.save("rx_aprs", &app_settings);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
@@ -290,7 +258,7 @@ void APRSTableView::on_pkt(const APRSPacketMessage* message) {
std::string source_formatted = packet.get_source_formatted();
std::string info_string = packet.get_stream_text();
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
auto& entry = ::on_packet(recent, packet.get_source());
entry.reset_age();
entry.inc_hit();
@@ -349,7 +317,7 @@ void APRSDetailsView::update() {
}
if (send_updates)
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0);
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0, 0);
}
APRSDetailsView::~APRSDetailsView() {
@@ -371,6 +339,7 @@ APRSDetailsView::APRSDetailsView(
entry_copy.source_formatted,
0, // entry_copy.pos.altitude,
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::HIDDEN,
entry_copy.pos.latitude,
entry_copy.pos.longitude,
0, /*entry_copy.velo.heading,*/
+18 -12
View File
@@ -26,9 +26,11 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_record_view.hpp" // DEBUG
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "ui_geomap.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "recent_entries.hpp"
#include "ui_tabview.hpp"
@@ -187,9 +189,14 @@ class APRSRxView : public View {
void on_data(uint32_t value, bool is_data);
bool reset_console = false;
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{
144390000 /* frequency */,
1750000 /* bandwidth */,
3072000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_aprs", app_settings::Mode::RX};
uint8_t console_color{0};
std::string str_log{""};
@@ -201,11 +208,12 @@ class APRSRxView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
OptionsField options_region{
{0 * 8, 0 * 8},
@@ -215,9 +223,9 @@ class APRSRxView : public View {
{"AUS", 2},
{"NZ ", 3}}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{3 * 8, 0 * 16},
};
nav_};
// DEBUG
RecordView record_view{
@@ -231,8 +239,6 @@ class APRSRxView : public View {
Console console{
{0, 2 * 16, 240, 240}};
void update_freq(rf::Frequency f);
std::unique_ptr<APRSLogger> logger{};
};
+6 -22
View File
@@ -26,7 +26,6 @@
#include "aprs.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "cpld_update.hpp"
#include "baseband_api.hpp"
#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
@@ -44,27 +43,20 @@ void APRSTXView::focus() {
}
APRSTXView::~APRSTXView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_aprs", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void APRSTXView::start_tx() {
// TODO: Clean up this API to take string_views to avoid allocations.
make_aprs_frame(
sym_source.value_string().c_str(), num_ssid_source.value(),
sym_dest.value_string().c_str(), num_ssid_dest.value(),
sym_source.to_string().c_str(), num_ssid_source.value(),
sym_dest.to_string().c_str(), num_ssid_dest.value(),
payload);
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
// text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
transmitter_model.set_tuning_frequency(persistent_memory::tuned_frequency());
transmitter_model.set_sampling_rate(AFSK_TX_SAMPLERATE);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_afsk_data(
@@ -97,14 +89,6 @@ APRSTXView::APRSTXView(NavigationView& nav) {
&button_set,
&tx_view});
// load app settings
auto rc = settings.load("tx_aprs", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
button_set.on_select = [this, &nav](Button&) {
text_prompt(
nav,
@@ -116,9 +100,9 @@ APRSTXView::APRSTXView(NavigationView& nav) {
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+12 -6
View File
@@ -24,12 +24,13 @@
#include "ui_textentry.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_transmitter.hpp"
#include "message.hpp"
#include "modems.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
namespace ui {
@@ -44,9 +45,13 @@ class APRSTXView : public View {
std::string title() const override { return "APRS TX"; };
private:
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
144390000 /* frequency */,
1750000 /* bandwidth */,
AFSK_TX_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_aprs", app_settings::Mode::TX};
std::string payload{""};
@@ -64,7 +69,8 @@ class APRSTXView : public View {
SymField sym_source{
{7 * 8, 1 * 16},
6,
SymField::SYMFIELD_ALPHANUM};
SymField::Type::Alpha};
NumberField num_ssid_source{
{19 * 8, 1 * 16},
2,
@@ -75,7 +81,7 @@ class APRSTXView : public View {
SymField sym_dest{
{7 * 8, 2 * 16},
6,
SymField::SYMFIELD_ALPHANUM};
SymField::Type::Alpha};
NumberField num_ssid_dest{
{19 * 8, 2 * 16},
+10 -26
View File
@@ -24,7 +24,6 @@
#include "string_format.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
@@ -137,13 +136,8 @@ void BHTView::on_tx_progress(const uint32_t progress, const bool done) {
}
BHTView::~BHTView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_bht", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
BHTView::BHTView(NavigationView& nav) {
@@ -157,21 +151,12 @@ BHTView::BHTView(NavigationView& nav) {
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_bht", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
field_speed.set_value(1);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
@@ -233,7 +218,6 @@ EPARView::EPARView(
field_city.set_value(0);
field_group.set_selected_index(2);
field_city.on_change = [this](int32_t) { generate_message(); };
field_group.on_change = [this](size_t, int32_t) { generate_message(); };
@@ -243,11 +227,11 @@ EPARView::EPARView(
size_t n = 0;
for (auto& relay_state : relay_states) {
relay_state.on_change = relay_state_fn;
relay_state.set_parent_rect({static_cast<Coord>(90 + (n * 36)),
80,
24, 24});
relay_state.set_options(relay_options);
relay_state.on_change = relay_state_fn; // NB: set after set_options to avoid startup call.
add_child(&relay_state);
n++;
}
@@ -316,11 +300,11 @@ XylosView::XylosView(
};
field_header_a.on_change = field_fn;
field_header_b.on_change = [this](int32_t) { generate_message(); };
field_city.on_change = [this](int32_t) { generate_message(); };
field_family.on_change = [this](int32_t) { generate_message(); };
field_subfamily.on_change = [this](int32_t) { generate_message(); };
field_receiver.on_change = [this](int32_t) { generate_message(); };
field_header_b.on_change = field_fn;
field_city.on_change = field_fn;
field_family.on_change = field_fn;
field_subfamily.on_change = field_fn;
field_receiver.on_change = field_fn;
checkbox_wcsubfamily.on_select = [this](Checkbox&, bool v) {
field_subfamily.set_focusable(!v);
@@ -341,11 +325,11 @@ XylosView::XylosView(
size_t n = 0;
for (auto& relay_state : relay_states) {
relay_state.on_change = relay_state_fn;
relay_state.set_parent_rect({static_cast<Coord>(54 + (n * 36)),
134,
24, 24});
relay_state.set_options(relay_options);
relay_state.on_change = relay_state_fn; // NB: set after set_options to avoid startup call.
add_child(&relay_state);
n++;
}
+4 -4
View File
@@ -25,7 +25,6 @@
#include "ui_tabview.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "bht.hpp"
#include "bitmap.hpp"
@@ -33,6 +32,7 @@
#include "transmitter_model.hpp"
#include "encoders.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
namespace ui {
@@ -166,9 +166,9 @@ class BHTView : public View {
std::string title() const override { return "BHT TX"; };
private:
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_bht", app_settings::Mode::TX};
void on_tx_progress(const uint32_t progress, const bool done);
void start_tx();
+4 -34
View File
@@ -40,11 +40,8 @@ void BTLERxView::focus() {
field_frequency.focus();
}
void BTLERxView::update_freq(rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
}
BTLERxView::BTLERxView(NavigationView& nav) {
BTLERxView::BTLERxView(NavigationView& nav)
: nav_{nav} {
baseband::run_image(portapack::spi_flash::image_tag_btle_rx);
add_children({&rssi,
@@ -56,16 +53,7 @@ BTLERxView::BTLERxView(NavigationView& nav) {
&button_modem_setup,
&console});
// load app settings
auto rc = settings.load("rx_btle", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
}
// Auto-configure modem for LCR RX (will be removed later)
update_freq(2426000000);
// Auto-configure modem for LCR RX (TODO: remove later)
auto def_bell202 = &modem_defs[0];
persistent_memory::set_modem_baudrate(def_bell202->baudrate);
serial_format_t serial_format;
@@ -75,32 +63,18 @@ BTLERxView::BTLERxView(NavigationView& nav) {
serial_format.bit_order = LSB_FIRST;
persistent_memory::set_serial_format(serial_format);
field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(100);
field_frequency.on_change = [this](rf::Frequency f) {
update_freq(f);
};
field_frequency.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
update_freq(f);
field_frequency.set_value(f);
};
};
button_modem_setup.on_select = [&nav](Button&) {
nav.push<ModemSetupView>();
};
// Auto-configure modem for LCR RX (will be removed later)
baseband::set_btle(persistent_memory::modem_baudrate(), 8, 0, false);
baseband::set_btlerx(channel_number);
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
receiver_model.set_sampling_rate(4000000);
receiver_model.set_baseband_bandwidth(4000000);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.enable();
}
@@ -148,10 +122,6 @@ void BTLERxView::on_data(uint32_t value, bool is_data) {
}
BTLERxView::~BTLERxView() {
// save app settings
app_settings.rx_frequency = field_frequency.value();
settings.save("rx_btle", &app_settings);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
+17 -13
View File
@@ -27,8 +27,10 @@
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "ui_record_view.hpp" // DEBUG
#include "radio_state.hpp"
#include "ui_record_view.hpp"
#include "utility.hpp"
@@ -46,13 +48,20 @@ class BTLERxView : public View {
private:
void on_data(uint32_t value, bool is_data);
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
NavigationView& nav_;
RxRadioState radio_state_{
2426000000 /* frequency */,
4000000 /* bandwidth */,
4000000 /* sampling rate */,
ReceiverModel::Mode::WidebandFMAudio};
app_settings::SettingsManager settings_{
"rx_btle", app_settings::Mode::RX};
uint8_t console_color{0};
uint32_t prev_value{0};
static constexpr uint8_t channel_number = 37;
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
@@ -60,15 +69,13 @@ class BTLERxView : public View {
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
{21 * 8, 0, 6 * 8, 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
};
{21 * 8, 5, 6 * 8, 4}};
FrequencyField field_frequency{
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
};
nav_};
Button button_modem_setup{
{240 - 12 * 8, 1 * 16, 96, 24},
@@ -77,9 +84,6 @@ class BTLERxView : public View {
Console console{
{0, 4 * 16, 240, 240}};
void update_freq(rf::Frequency f);
// void on_data_afsk(const AFSKDataMessage& message);
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
+255 -37
View File
@@ -20,15 +20,21 @@
*/
#include "ui_debug.hpp"
#include "debug.hpp"
#include "ch.h"
#include "radio.hpp"
#include "string_format.hpp"
#include "crc.hpp"
#include "audio.hpp"
#include "ui_sd_card_debug.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "ui_styles.hpp"
#include "ui_painter.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
@@ -118,12 +124,12 @@ void TemperatureWidget::paint(Painter& painter) {
}
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
/*It seems to be a temperature difference of 25C*/
return -40 + (sensor_value * 4.31) + 25; // max2837 datasheet temp 25ºC has sensor value: 15
// 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 - 1) + "C";
return to_string_dec_int(temperature, temp_len - 2) + STR_DEGREES_C;
}
Coord TemperatureWidget::screen_y(
@@ -153,11 +159,8 @@ void TemperatureView::focus() {
/* RegistersWidget *******************************************************/
RegistersWidget::RegistersWidget(
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader)
: Widget{},
config(std::move(config)),
reader(std::move(reader)) {
RegistersWidgetConfig&& config)
: Widget{}, config(std::move(config)), page_number(0) {
}
void RegistersWidget::update() {
@@ -173,12 +176,15 @@ void RegistersWidget::paint(Painter& painter) {
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
const auto pos = screen_pos();
const std::string spaces(config.legend_length(), ' ');
for (uint32_t i = 0; i < config.registers_per_page; i += config.registers_per_row()) {
uint32_t r = page_number * config.registers_per_page + i;
for (size_t i = 0; i < config.registers_count; i += config.registers_per_row()) {
const Point offset{
left, static_cast<int>((i / config.registers_per_row()) * row_height)};
const auto text = to_string_hex(i, config.legend_length());
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(r, config.legend_length());
painter.draw_string(
pos + offset,
style().invert(),
@@ -190,15 +196,16 @@ void RegistersWidget::draw_values(
const Coord left,
Painter& painter) {
const auto pos = screen_pos();
const std::string spaces(config.value_length(), ' ');
for (uint32_t i = 0; i < config.registers_per_page; i++) {
uint32_t r = page_number * config.registers_per_page + i;
for (size_t i = 0; i < config.registers_count; i++) {
const Point offset = {
static_cast<int>(left + config.legend_width() + 8 + (i % config.registers_per_row()) * (config.value_width() + 8)),
static_cast<int>((i / config.registers_per_row()) * row_height)};
const auto value = reader(i);
const auto text = to_string_hex(value, config.value_length());
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(reg_read(r), config.value_length());
painter.draw_string(
pos + offset,
style(),
@@ -206,19 +213,61 @@ void RegistersWidget::draw_values(
}
}
uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
if (register_number < config.registers_count) {
switch (config.chip_type) {
case CT_PMEM:
return portapack::persistent_memory::pmem_data_word(register_number / 4) >> (register_number % 4 * 8);
case CT_RFFC5072:
return radio::debug::first_if::register_read(register_number);
case CT_MAX283X:
return radio::debug::second_if::register_read(register_number);
case CT_SI5351:
return portapack::clock_generator.read_register(register_number);
case CT_AUDIO:
return audio::debug::reg_read(register_number);
}
}
return 0xFFFF;
}
void RegistersWidget::reg_write(const uint32_t register_number, const uint32_t value) {
if (register_number < config.registers_count) {
switch (config.chip_type) {
case CT_PMEM:
break;
case CT_RFFC5072:
radio::debug::first_if::register_write(register_number, value);
break;
case CT_MAX283X:
radio::debug::second_if::register_write(register_number, value);
break;
case CT_SI5351:
portapack::clock_generator.write_register(register_number, value);
break;
case CT_AUDIO:
audio::debug::reg_write(register_number, value);
break;
}
}
}
/* RegistersView *********************************************************/
RegistersView::RegistersView(
NavigationView& nav,
const std::string& title,
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader)
: registers_widget{std::move(config), std::move(reader)} {
RegistersWidgetConfig&& config)
: registers_widget{std::move(config)} {
add_children({
&text_title,
&registers_widget,
&button_update,
&button_done,
&labels,
&field_write_reg_num,
&field_write_data_val,
&button_write,
});
button_update.on_select = [this](Button&) {
@@ -227,16 +276,38 @@ RegistersView::RegistersView(
button_done.on_select = [&nav](Button&) { nav.pop(); };
registers_widget.set_parent_rect({0, 48, 240, 192});
registers_widget.set_page(0);
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
static_cast<int>(title.size()) * 8, 16});
text_title.set(title);
field_write_reg_num.on_change = [this](SymField&) {
field_write_data_val.set_value(this->registers_widget.reg_read(field_write_reg_num.to_integer()));
field_write_data_val.set_dirty();
};
const auto value = registers_widget.reg_read(0);
field_write_data_val.set_value(value);
button_write.set_style(&Styles::red);
button_write.on_select = [this](Button&) {
this->registers_widget.reg_write(field_write_reg_num.to_integer(), field_write_data_val.to_integer());
this->registers_widget.update();
};
}
void RegistersView::focus() {
button_done.focus();
}
bool RegistersView::on_encoder(const EncoderEvent delta) {
registers_widget.set_page(std::max(0ul, std::min(registers_widget.page_count() - 1, registers_widget.page() + delta)));
registers_widget.update();
return true;
}
/* ControlsSwitchesWidget ************************************************/
void ControlsSwitchesWidget::on_show() {
@@ -247,36 +318,41 @@ void ControlsSwitchesWidget::on_show() {
bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
key_event_mask = 1 << toUType(key);
long_press_key_event_mask = key_is_long_pressed(key) ? key_event_mask : 0;
return true;
}
void ControlsSwitchesWidget::paint(Painter& painter) {
const auto pos = screen_pos();
const std::array<Rect, 8> button_rects{{
const std::array<Rect, 9> button_rects{{
{64, 32, 16, 16}, // Right
{0, 32, 16, 16}, // Left
{32, 64, 16, 16}, // Down
{32, 0, 16, 16}, // Up
{32, 32, 16, 16}, // Select
{96, 0, 16, 16}, // Dfu
{16, 96, 16, 16}, // Encoder phase 0
{48, 96, 16, 16}, // Encoder phase 1
{96, 0, 16, 16}, // Dfu
{96, 64, 16, 16}, // Touch
}};
for (const auto r : button_rects) {
painter.fill_rectangle(r + pos, Color::blue());
}
if (get_touch_frame().touch)
painter.fill_rectangle(button_rects[8] + pos, Color::yellow());
const std::array<Rect, 8> raw_rects{{
{64 + 1, 32 + 1, 16 - 2, 16 - 2}, // Right
{0 + 1, 32 + 1, 16 - 2, 16 - 2}, // Left
{32 + 1, 64 + 1, 16 - 2, 16 - 2}, // Down
{32 + 1, 0 + 1, 16 - 2, 16 - 2}, // Up
{32 + 1, 32 + 1, 16 - 2, 16 - 2}, // Select
{96 + 1, 0 + 1, 16 - 2, 16 - 2}, // Dfu
{16 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 0
{48 + 1, 96 + 1, 16 - 2, 16 - 2}, // Encoder phase 1
{96 + 1, 0 + 1, 16 - 2, 16 - 2}, // Dfu
}};
auto switches_raw = control::debug::switches();
@@ -320,6 +396,14 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
switches_event >>= 1;
}
switches_event = long_press_key_event_mask;
for (const auto r : events_rects) {
if (switches_event & 1)
painter.fill_rectangle(r + pos, Color::cyan());
switches_event >>= 1;
}
}
void ControlsSwitchesWidget::on_frame_sync() {
@@ -330,12 +414,21 @@ void ControlsSwitchesWidget::on_frame_sync() {
DebugControlsView::DebugControlsView(NavigationView& nav) {
add_children({
&text_title,
&labels,
&switches_widget,
&options_switches_mode,
&button_done,
});
button_done.on_select = [&nav](Button&) { nav.pop(); };
button_done.on_select = [&nav](Button&) {
set_switches_long_press_config(0);
nav.pop();
};
options_switches_mode.on_change = [this](size_t, OptionsField::value_t v) {
(void)v;
set_switches_long_press_config(options_switches_mode.selected_index_value());
};
}
void DebugControlsView::focus() {
@@ -348,18 +441,10 @@ DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {
const char* max283x = hackrf_r9 ? "MAX2839" : "MAX2837";
const char* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
add_items({
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(
"RFFC5072", RegistersWidgetConfig{31, 16},
[](const size_t register_number) { return radio::debug::first_if::register_read(register_number); }); }},
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(
max283x, RegistersWidgetConfig{32, 10},
[](const size_t register_number) { return radio::debug::second_if::register_read(register_number); }); }},
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(
si5351x, RegistersWidgetConfig{96, 8},
[](const size_t register_number) { return portapack::clock_generator.read_register(register_number); }); }},
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(
audio::debug::codec_name(), RegistersWidgetConfig{audio::debug::reg_count(), audio::debug::reg_bits()},
[](const size_t register_number) { return audio::debug::reg_read(register_number); }); }},
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 32, 10}); }},
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
});
set_max_rows(2); // allow wider buttons
}
@@ -371,16 +456,149 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({
{"Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
{"Debug Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { portapack::persistent_memory::debug_dump(); }},
{"M0 Stack Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { stack_dump(); }},
{"Memory Dump", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryDumpView>(); }},
{"Memory Usage", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
{"Pers. Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
//{ "Radio State", ui::Color::white(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
{"Peripherals", ui::Color::dark_cyan(), &bitmap_icon_peripherals, [&nav]() { nav.push<DebugPeripheralsMenuView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Buttons Test", ui::Color::dark_cyan(), &bitmap_icon_controls, [&nav]() { nav.push<DebugControlsView>(); }},
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav)) {
add_item(gridItem);
};
set_max_rows(2); // allow wider buttons
}
/* DebugMemoryDumpView *********************************************************/
DebugMemoryDumpView::DebugMemoryDumpView(NavigationView& nav) {
add_children({
&button_dump,
&button_read,
&button_write,
&button_done,
&labels,
&field_starting_address,
&field_byte_count,
&field_rw_address,
&field_data_value,
});
button_done.on_select = [&nav](Button&) { nav.pop(); };
button_dump.on_select = [this](Button&) {
if (field_byte_count.to_integer() != 0)
memory_dump((uint32_t*)field_starting_address.to_integer(), ((uint32_t)field_byte_count.to_integer() + 3) / 4, false);
};
button_read.on_select = [this](Button&) {
field_data_value.set_value(*(uint32_t*)field_rw_address.to_integer());
field_data_value.set_dirty();
};
button_write.set_style(&Styles::red);
button_write.on_select = [this](Button&) {
*(uint32_t*)field_rw_address.to_integer() = (uint32_t)field_data_value.to_integer();
};
}
void DebugMemoryDumpView::focus() {
button_done.focus();
}
/* DebugPmemView *********************************************************/
DebugPmemView::DebugPmemView(NavigationView& nav)
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({&registers_widget, &text_checksum, &text_checksum2, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
button_ok.on_select = [&nav](Button&) {
nav.pop();
};
update();
}
bool DebugPmemView::on_encoder(const EncoderEvent delta) {
registers_widget.set_page(std::max(0ul, std::min((uint32_t)page_count - 1, registers_widget.page() + delta)));
update();
return true;
}
void DebugPmemView::focus() {
button_ok.focus();
}
void DebugPmemView::update() {
registers_widget.update();
}
/* DebugScreenTest ****************************************************/
DebugScreenTest::DebugScreenTest(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
}
bool DebugScreenTest::on_key(const KeyEvent key) {
Painter painter;
switch (key) {
case KeyEvent::Select:
nav_.pop();
break;
case KeyEvent::Down:
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
break;
case KeyEvent::Left:
pen_color = semirand();
break;
default:
break;
}
return true;
}
bool DebugScreenTest::on_encoder(EncoderEvent delta) {
pen_size = clip<int32_t>(pen_size + delta, 1, screen_width);
return true;
}
bool DebugScreenTest::on_touch(const TouchEvent event) {
Painter painter;
pen_pos = event.point;
painter.fill_rectangle({pen_pos.x() - pen_size / 2, pen_pos.y() - pen_size / 2, pen_size, pen_size}, pen_color);
return true;
}
uint16_t DebugScreenTest::semirand() {
static uint64_t seed{0x0102030405060708};
seed = seed * 137;
seed = (seed >> 1) | ((seed & 0x01) << 63);
return (uint16_t)seed;
}
void DebugScreenTest::paint(Painter& painter) {
painter.fill_rectangle({0, 16, screen_width, screen_height - 16}, Color::white());
painter.draw_string({10 * 8, screen_height / 2}, Styles::white, "Use Stylus");
pen_color = semirand();
}
/* DebugLCRView *******************************************************/
/*DebugLCRView::DebugLCRView(NavigationView& nav, std::string lcr_string) {
std::string debug_text;
+155 -23
View File
@@ -30,6 +30,8 @@
#include "rffc507x.hpp"
#include "portapack.hpp"
#include "memory_map.hpp"
#include "irq_controls.hpp"
#include <functional>
#include <utility>
@@ -46,8 +48,8 @@ class DebugMemoryView : public View {
private:
Text text_title{
{96, 96, 48, 16},
"Memory",
{72, 96, 96, 16},
"Memory Usage",
};
Text text_label_m0_core_free{
@@ -103,7 +105,7 @@ class TemperatureWidget : public Widget {
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 = 4; // Now scale shows up to 4 chars ("-10C")
static constexpr int temp_len = 5; // Now scale shows up to 5 chars ("-10ºC")
};
class TemperatureView : public View {
@@ -129,9 +131,19 @@ class TemperatureView : public View {
"Done"};
};
typedef enum {
CT_PMEM,
CT_RFFC5072,
CT_MAX283X,
CT_SI5351,
CT_AUDIO,
} chip_type_t;
struct RegistersWidgetConfig {
size_t registers_count;
size_t register_bits;
chip_type_t chip_type;
uint32_t registers_count;
uint32_t registers_per_page;
uint32_t register_bits;
constexpr size_t legend_length() const {
return (registers_count >= 0x10) ? 2 : 1;
@@ -172,17 +184,22 @@ struct RegistersWidgetConfig {
class RegistersWidget : public Widget {
public:
RegistersWidget(
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader);
RegistersWidget(RegistersWidgetConfig&& config);
void update();
void paint(Painter& painter) override;
uint32_t reg_read(const uint32_t register_number);
void reg_write(const uint32_t register_number, const uint32_t value);
void set_page(int32_t value) { page_number = value; }
uint32_t page(void) { return page_number; }
uint32_t page_count(void) { return (config.registers_count + config.registers_per_page - 1) / config.registers_per_page; }
private:
const RegistersWidgetConfig config;
const std::function<uint32_t(const size_t register_number)> reader;
uint32_t page_number;
static constexpr size_t row_height = 16;
@@ -192,13 +209,9 @@ class RegistersWidget : public Widget {
class RegistersView : public View {
public:
RegistersView(
NavigationView& nav,
const std::string& title,
RegistersWidgetConfig&& config,
std::function<uint32_t(const size_t register_number)>&& reader);
void focus();
RegistersView(NavigationView& nav, const std::string& title, RegistersWidgetConfig&& config);
void focus() override;
bool on_encoder(const EncoderEvent delta) override;
private:
Text text_title{};
@@ -206,12 +219,30 @@ class RegistersView : public View {
RegistersWidget registers_widget;
Button button_update{
{16, 256, 96, 24},
{16, 280, 96, 24},
"Update"};
Button button_done{
{128, 256, 96, 24},
{128, 280, 96, 24},
"Done"};
Button button_write{
{144, 248, 80, 20},
"Write"};
Labels labels{
{{1 * 8, 248}, "Reg:", Color::light_grey()},
{{8 * 8, 248}, "Data:", Color::light_grey()}};
SymField field_write_reg_num{
{5 * 8, 248},
2,
SymField::Type::Hex};
SymField field_write_data_val{
{13 * 8, 248},
4,
SymField::Type::Hex};
};
class ControlsSwitchesWidget : public Widget {
@@ -219,7 +250,8 @@ class ControlsSwitchesWidget : public Widget {
ControlsSwitchesWidget(
Rect parent_rect)
: Widget{parent_rect},
key_event_mask(0) {
key_event_mask(0),
long_press_key_event_mask{0} {
set_focusable(true);
}
@@ -230,6 +262,7 @@ class ControlsSwitchesWidget : public Widget {
private:
uint8_t key_event_mask;
uint8_t long_press_key_event_mask;
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
@@ -249,20 +282,119 @@ class DebugControlsView : public View {
std::string title() const override { return "Buttons Test"; };
private:
Text text_title{
{64, 16, 184, 16},
"Controls State",
};
Labels labels{
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
ControlsSwitchesWidget switches_widget{
{80, 80, 80, 112},
};
OptionsField options_switches_mode{
{17 * 8, 14 * 16},
8,
{
{"Disabled", 0},
{"Enabled", 0xFF}, // all KeyEvent bits to long-press mode
}};
Button button_done{
{72, 264, 96, 24},
"Done"};
};
class DebugMemoryDumpView : public View {
public:
DebugMemoryDumpView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Memory Dump"; };
private:
Button button_dump{
{72, 4 * 16, 96, 24},
"Dump"};
Button button_read{
{16, 11 * 16, 96, 24},
"Read"};
Button button_write{
{128, 11 * 16, 96, 24},
"Write"};
Button button_done{
{128, 240, 96, 24},
"Done"};
Labels labels{
{{5 * 8, 1 * 16}, "Dump Range to File", Color::yellow()},
{{0 * 8, 2 * 16}, "Starting Address: 0x", Color::light_grey()},
{{0 * 8, 3 * 16}, "Byte Count: 0x", Color::light_grey()},
{{3 * 8, 8 * 16}, "Read/Write Single Word", Color::yellow()},
{{0 * 8, 9 * 16}, "Memory Address: 0x", Color::light_grey()},
{{0 * 8, 10 * 16}, "Data Value: 0x", Color::light_grey()}};
SymField field_starting_address{
{20 * 8, 2 * 16},
8,
SymField::Type::Hex};
SymField field_byte_count{
{20 * 8, 3 * 16},
8,
SymField::Type::Hex};
SymField field_rw_address{
{20 * 8, 9 * 16},
8,
SymField::Type::Hex};
SymField field_data_value{
{20 * 8, 10 * 16},
8,
SymField::Type::Hex};
};
class DebugPmemView : public View {
public:
DebugPmemView(NavigationView& nav);
void focus() override;
bool on_encoder(const EncoderEvent delta) override;
std::string title() const override { return "P.Mem Debug"; }
private:
static constexpr uint8_t page_size{96}; // Must be multiply of 4 otherwise bit shifting for register view wont work properly
static constexpr uint8_t page_count{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size};
RegistersWidget registers_widget;
Text text_checksum{{16, 232, 208, 16}};
Text text_checksum2{{16, 248, 208, 16}};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
"OK",
};
void update();
};
class DebugScreenTest : public View {
public:
DebugScreenTest(NavigationView& nav);
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
bool on_touch(TouchEvent event) override;
uint16_t semirand();
void paint(Painter& painter) override;
private:
NavigationView& nav_;
Point pen_pos{};
Color pen_color{0};
int16_t pen_size{10};
};
/*class DebugLCRView : public View {
public:
DebugLCRView(NavigationView& nav, std::string lcrstring);
+61 -1
View File
@@ -22,6 +22,7 @@
#include "ui_dfu_menu.hpp"
#include "portapack_shared_memory.hpp"
#include "performance_counter.hpp"
#include "portapack.hpp"
namespace ui {
@@ -47,7 +48,7 @@ void DfuMenu::paint(Painter& painter) {
text_info_line_3.set(to_string_dec_uint(utilisation, 6));
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_cpu_usage, 6));
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
text_info_line_8.set(to_string_dec_uint(chTimeNow() / 1000, 6));
@@ -80,4 +81,63 @@ void DfuMenu::paint(Painter& painter) {
ui::Color::dark_cyan());
}
DfuMenu2::DfuMenu2(NavigationView& nav)
: nav_(nav) {
add_children({&text_head,
&labels,
&text_info_line_1,
&text_info_line_2,
&text_info_line_3,
&text_info_line_4,
&text_info_line_5,
&text_info_line_6,
&text_info_line_7,
&text_info_line_8,
&text_info_line_9,
&text_info_line_10,
&text_info_line_11});
}
void DfuMenu2::paint(Painter& painter) {
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
text_info_line_4.set(to_string_dec_uint(((uint32_t)shared_memory.m4_performance_counter) * 100 / 127, 10));
text_info_line_5.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
text_info_line_8.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
text_info_line_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
constexpr auto margin = 5;
constexpr auto lines = 11 + 2;
painter.fill_rectangle(
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
{19 * CHARACTER_WIDTH + margin * 2, lines * LINE_HEIGHT + margin * 2}},
ui::Color::black());
painter.fill_rectangle(
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
ui::Color::dark_cyan());
painter.fill_rectangle(
{{24 * CHARACTER_WIDTH + margin, 3 * LINE_HEIGHT - margin},
{CHARACTER_WIDTH, lines * LINE_HEIGHT + margin * 2}},
ui::Color::dark_cyan());
painter.fill_rectangle(
{{4 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin - 8},
{21 * CHARACTER_WIDTH + margin * 2, 8}},
ui::Color::dark_cyan());
painter.fill_rectangle(
{{4 * CHARACTER_WIDTH - margin, (lines + 3) * LINE_HEIGHT + margin},
{21 * CHARACTER_WIDTH + margin * 2, 8}},
ui::Color::dark_cyan());
}
} /* namespace ui */
+47 -8
View File
@@ -57,14 +57,53 @@ class DfuMenu : public View {
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_4{{15 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_5{{15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 5 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_3{{15 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_4{{15 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_5{{15 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_6{{15 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_7{{15 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
class DfuMenu2 : public View {
public:
DfuMenu2(NavigationView& nav);
~DfuMenu2() = default;
void paint(Painter& painter) override;
private:
NavigationView& nav_;
Text text_head{{6 * CHARACTER_WIDTH, 3 * LINE_HEIGHT, 14 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, "Radio Settings"};
Labels labels{
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "RX Satu%:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()},
};
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_3{{14 * CHARACTER_WIDTH, 7 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_4{{14 * CHARACTER_WIDTH, 8 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_5{{14 * CHARACTER_WIDTH, 9 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_6{{14 * CHARACTER_WIDTH, 10 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_7{{14 * CHARACTER_WIDTH, 11 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
} /* namespace ui */
+42 -47
View File
@@ -24,7 +24,6 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "cpld_update.hpp"
using namespace portapack;
@@ -50,7 +49,6 @@ EncodersConfigView::EncodersConfigView(
&field_clk_step,
&field_frameduration,
&field_frameduration_step,
&symfield_word,
&text_format,
&waveform});
@@ -65,10 +63,6 @@ EncodersConfigView::EncodersConfigView(
options_enctype.set_options(enc_options);
options_enctype.set_selected_index(0);
symfield_word.on_change = [this]() {
generate_frame();
};
// Selecting input clock changes symbol and word duration
field_clk.on_change = [this](int32_t value) {
// value is in kHz, new_value is in us
@@ -99,33 +93,46 @@ void EncodersConfigView::focus() {
}
void EncodersConfigView::on_type_change(size_t index) {
std::string format_string = "";
size_t word_length;
char symbol_type;
// Remove existing SymField controls.
for (auto& symfield : symfields_word)
remove_child(symfield.get());
symfields_word.clear();
encoder_def = &encoder_defs[index];
field_clk.set_value(encoder_def->default_speed / 1000);
field_repeat_min.set_value(encoder_def->repeat_min);
// SymField setup
word_length = encoder_def->word_length;
symfield_word.set_length(word_length);
size_t n = 0, i = 0;
while (n < word_length) {
symbol_type = encoder_def->word_format[i++];
if (symbol_type == 'A') {
symfield_word.set_symbol_list(n++, encoder_def->address_symbols);
format_string += 'A';
} else if (symbol_type == 'D') {
symfield_word.set_symbol_list(n++, encoder_def->data_symbols);
format_string += 'D';
// Add new SymFields.
Point pos{2 * 8, 9 * 8};
std::string format_string;
uint8_t word_length = encoder_def->word_length;
auto on_change_handler = [this](SymField&) {
generate_frame();
};
for (uint8_t i = 0; i < word_length; i++) {
auto symbol_type = encoder_def->word_format[i];
symfields_word.push_back(std::make_unique<SymField>(pos, 1));
auto& symfield = symfields_word.back();
symfield->on_change = on_change_handler;
switch (symbol_type) {
case 'A':
symfield->set_symbol_list(encoder_def->address_symbols);
format_string += 'A';
break;
case 'D':
symfield->set_symbol_list(encoder_def->data_symbols);
format_string += 'D';
break;
}
add_child(symfield.get());
pos += Point{8, 0};
}
// Ugly :( Pad to erase
format_string.append(24 - format_string.size(), ' ');
text_format.set(format_string);
generate_frame();
@@ -147,17 +154,18 @@ void EncodersConfigView::draw_waveform() {
}
void EncodersConfigView::generate_frame() {
size_t i = 0;
frame_fragments.clear();
size_t i = 0;
for (auto c : encoder_def->word_format) {
if (c == 'S')
frame_fragments += encoder_def->sync;
else if (!c)
else if (c == '\0')
break;
else
frame_fragments += encoder_def->bit_format[symfield_word.get_sym(i++)];
else {
auto offset = symfields_word[i++]->get_offset(0);
frame_fragments += encoder_def->bit_format[offset];
}
}
draw_waveform();
@@ -200,13 +208,8 @@ void EncodersView::focus() {
}
EncodersView::~EncodersView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_ook", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // ghost signal c/m to the problem at the exit .
baseband::shutdown(); // better this function after load_sram()
baseband::shutdown();
}
void EncodersView::update_progress() {
@@ -266,8 +269,9 @@ void EncodersView::start_tx(const bool scan) {
repeat_index = 1;
update_progress();
transmitter_model.set_sampling_rate(OOK_SAMPLERATE);
transmitter_model.set_baseband_bandwidth(1750000);
/* Setting TX LPF 1M75 in this TX OOK App , We got same results as fw 1.7.4
* Looking max BW of this app, we tested , Selecting OOK type 145026 with CLK 455K and max DEV. 150k,
* and we got BW +-2Mhz , with that TX LPF 1M75, it is fine.*/
transmitter_model.enable();
baseband::set_ook_data(
@@ -290,19 +294,10 @@ EncodersView::EncodersView(
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_ook", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+12 -18
View File
@@ -27,6 +27,10 @@
#include "encoders.hpp"
#include "de_bruijn.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include <memory>
#include <vector>
using namespace encoders;
@@ -112,10 +116,7 @@ class EncodersConfigView : public View {
{"100", 100},
{"1000", 1000}}};
SymField symfield_word{
{2 * 8, 9 * 8},
20,
SymField::SYMFIELD_DEF};
std::vector<std::unique_ptr<SymField>> symfields_word{};
Text text_format{
{2 * 8, 11 * 8, 24 * 8, 16},
@@ -183,9 +184,13 @@ class EncodersView : public View {
SCAN
};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
433920000 /* frequency */,
1750000 /* bandwidth */,
OOK_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_ook", app_settings::Mode::TX};
tx_modes tx_mode = IDLE;
uint32_t repeat_index{0};
@@ -195,17 +200,6 @@ class EncodersView : public View {
void start_tx(const bool scan);
void on_tx_progress(const uint32_t progress, const bool done);
/*const Style style_address {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_data {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};*/
Rect view_rect = {0, 4 * 8, 240, 168};
EncodersConfigView view_config{nav_, view_rect};
+211 -124
View File
@@ -26,6 +26,11 @@
#include <algorithm>
#include "ui_fileman.hpp"
#include "ui_playlist.hpp"
#include "ui_remote.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_text_editor.hpp"
#include "ui_iq_trim.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
#include "event_m0.hpp"
@@ -33,6 +38,17 @@
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
static const fs::path txt_ext{u".TXT"};
static const fs::path ppl_ext{u".PPL"};
static const fs::path c8_ext{u".C8"};
static const fs::path c16_ext{u".C16"};
static const fs::path cxx_ext{u".C*"};
static const fs::path png_ext{u".PNG"};
static const fs::path bmp_ext{u".BMP"};
static const fs::path rem_ext{u".REM"};
} // namespace ui
namespace {
using namespace ui;
@@ -45,36 +61,18 @@ std::string truncate(const fs::path& path, size_t max_length) {
return ::truncate(path.string(), max_length);
}
// Case insensitive path equality on underlying "native" string.
bool iequal(
const fs::path& lhs,
const fs::path& rhs) {
const auto& lhs_str = lhs.native();
const auto& rhs_str = rhs.native();
// NB: Not correct for Unicode/locales.
if (lhs_str.length() == rhs_str.length()) {
for (size_t i = 0; i < lhs_str.length(); ++i)
if (towupper(lhs_str[i]) != towupper(rhs_str[i]))
return false;
return true;
}
return false;
}
// Inserts the entry into the entry list sorted directories first then by file name.
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
auto it = std::lower_bound(std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
if (lhs.is_directory && !rhs.is_directory)
return true;
else if (!lhs.is_directory && rhs.is_directory)
return false;
else
return lhs.path < rhs.path;
});
auto it = std::lower_bound(
std::begin(entries), std::end(entries), entry,
[](const fileman_entry& lhs, const fileman_entry& rhs) {
if (lhs.is_directory && !rhs.is_directory)
return true;
else if (!lhs.is_directory && rhs.is_directory)
return false;
else
return lhs.path < rhs.path;
});
entries.insert(it, std::move(entry));
}
@@ -83,19 +81,17 @@ void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
fs::path get_partner_file(fs::path path) {
if (fs::is_directory(path))
return {};
const fs::path txt_path{u".TXT"};
const fs::path c16_path{u".C16"};
auto ext = path.extension();
if (iequal(ext, txt_path))
ext = c16_path;
else if (iequal(ext, c16_path))
ext = txt_path;
else
if (is_cxx_capture_file(path))
path.replace_extension(txt_ext);
else if (path_iequal(ext, txt_ext)) {
path.replace_extension(c8_ext);
if (!fs::file_exists(path))
path.replace_extension(c16_ext);
} else
return {};
path.replace_extension(ext);
return fs::file_exists(path) && !fs::is_directory(path) ? path : fs::path{};
}
@@ -113,14 +109,17 @@ bool partner_file_prompt(
if (partner.empty())
return false;
nav.push_under_current<ModalMessageView>(
"Partner File",
partner.filename().string() + "\n" + action_name + " this file too?",
YESNO,
[&nav, partner, on_partner_action](bool choice) {
if (on_partner_action)
on_partner_action(partner, choice);
});
// Display the continuation UI once the current has been popped.
nav.set_on_pop([&nav, partner, action_name, on_partner_action] {
nav.display_modal(
"Partner File",
partner.filename().string() + "\n" + action_name + " this file too?",
YESNO,
[&nav, partner, on_partner_action](bool choice) {
if (on_partner_action)
on_partner_action(partner, choice);
});
});
return true;
}
@@ -151,16 +150,17 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
current_path = dir_path;
entry_list.clear();
auto filtering = !extension_filter.empty();
bool cxx_file = path_iequal(cxx_ext, extension_filter);
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
// Hide files starting with '.' (hidden / tmp).
if (is_hidden_file(entry.path()))
if (!show_hidden_files && is_hidden_file(entry.path()))
continue;
if (fs::is_regular_file(entry.status())) {
if (!filtering || iequal(entry.path().extension(), extension_filter))
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path(), 0, true});
@@ -191,11 +191,10 @@ FileManBaseView::FileManBaseView(
extension_filter{filter} {
add_children({&labels,
&text_current,
&text_info,
&button_exit});
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
button_exit.on_select = [this](Button&) {
nav_.pop();
};
if (!sdcIsCardInserted(&SDCD1)) {
@@ -256,8 +255,13 @@ void FileManBaseView::refresh_list() {
auto entry_name = truncate(entry.path, 20);
if (entry.is_directory) {
auto size_str =
(entry.path == parent_dir_path)
? ""
: to_string_dec_uint(file_count(current_path / entry.path));
menu_view.add_item(
{entry_name,
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
ui::Color::yellow(),
&bitmap_icon_dir,
[this](KeyEvent key) {
@@ -284,7 +288,6 @@ void FileManBaseView::refresh_list() {
break;
}
text_info.set(menu_view.item_count() >= max_items_shown ? "Too many files!" : "");
menu_view.set_highlighted(prev_highlight);
}
@@ -298,7 +301,7 @@ const FileManBaseView::file_assoc_t& FileManBaseView::get_assoc(
size_t index = 0;
for (; index < file_types.size() - 1; ++index)
if (iequal(ext, file_types[index].extension))
if (path_iequal(ext, file_types[index].extension))
return file_types[index];
// Default to last entry in the list.
@@ -326,9 +329,9 @@ FileLoadView::FileLoadView(
if (get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else {
nav_.pop();
if (on_changed)
on_changed(get_selected_full_path());
nav_.pop();
}
};
}
@@ -348,7 +351,6 @@ FileSaveView::FileSaveView(
file_{ file }
{
add_children({
&labels,
&text_path,
&button_edit_path,
&text_name,
@@ -397,53 +399,79 @@ void FileSaveView::refresh_widgets() {
*/
/* FileManagerView ***********************************************************/
void FileManagerView::on_rename() {
auto& entry = get_selected_entry();
name_buffer = entry.path.filename().string();
uint32_t cursor_pos = (uint32_t)name_buffer.length();
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
button_new_dir.hidden(v);
button_new_file.hidden(v);
set_dirty();
}
void FileManagerView::on_rename(std::string_view hint) {
auto& entry = get_selected_entry();
// Append the hint to the filename stem as a rename suggestion.
name_buffer = entry.path.stem().string();
if (!hint.empty())
name_buffer += "_" + std::string{hint};
name_buffer += entry.path.extension().string();
// Set the rename cursor to before the extension to make renaming simpler.
uint32_t cursor_pos = (uint32_t)name_buffer.length();
if (auto pos = name_buffer.find_last_of(".");
pos != name_buffer.npos && !entry.is_directory)
cursor_pos = pos;
text_prompt(nav_, name_buffer, cursor_pos, max_filename_length,
[this](std::string& renamed) {
auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path);
text_prompt(
nav_, name_buffer, cursor_pos, max_filename_length,
[this](std::string& renamed) {
auto renamed_path = fs::path{renamed};
rename_file(get_selected_full_path(), current_path / renamed_path);
auto has_partner = partner_file_prompt(nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
});
if (!has_partner)
reload_current();
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Rename",
[this, renamed_path](const fs::path& partner, bool should_rename) mutable {
if (should_rename) {
auto new_name = renamed_path.replace_extension(partner.extension());
rename_file(partner, current_path / new_name);
}
reload_current();
});
if (!has_partner)
reload_current();
});
}
void FileManagerView::on_delete() {
if (is_directory(get_selected_full_path()) && !is_empty_directory(get_selected_full_path())) {
nav_.display_modal("Delete", "Directory not empty!");
return;
}
auto name = get_selected_entry().path.filename().string();
nav_.push<ModalMessageView>("Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
delete_file(get_selected_full_path());
nav_.push<ModalMessageView>(
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
delete_file(get_selected_full_path());
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
});
auto has_partner = partner_file_prompt(
nav_, get_selected_full_path(), "Delete",
[this](const fs::path& partner, bool should_delete) {
if (should_delete)
delete_file(partner);
reload_current();
});
if (!has_partner)
reload_current();
}
});
if (!has_partner)
reload_current();
}
});
}
void FileManagerView::on_new_dir() {
@@ -454,14 +482,6 @@ void FileManagerView::on_new_dir() {
});
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
});
}
void FileManagerView::on_paste() {
// TODO: handle partner file. Need to fix nav stack first.
auto new_name = get_unique_filename(current_path, clipboard_path.filename());
@@ -482,23 +502,48 @@ void FileManagerView::on_paste() {
reload_current();
}
void FileManagerView::on_new_file() {
name_buffer = "";
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
make_new_file(current_path / file_name);
reload_current();
});
}
bool FileManagerView::handle_file_open() {
if (!selected_is_valid())
return false;
auto path = get_selected_full_path();
auto ext = path.extension();
if (path_iequal(txt_ext, ext)) {
nav_.push<TextEditorView>(path);
return true;
} else if (is_cxx_capture_file(path) || path_iequal(ppl_ext, ext)) {
// TODO: Enough memory to push?
nav_.push<PlaylistView>(path);
return true;
} else if (path_iequal(png_ext, ext)) {
nav_.push<ScreenshotViewer>(path);
return true;
} else if (path_iequal(bmp_ext, ext)) {
nav_.push<SplashViewer>(path);
reload_current();
return true;
} else if (path_iequal(rem_ext, ext)) {
nav_.push<RemoteView>(path);
return true;
}
return false;
}
bool FileManagerView::selected_is_valid() const {
return !entry_list.empty() &&
get_selected_entry().path != parent_dir_path;
}
void FileManagerView::refresh_widgets(const bool v) {
button_rename.hidden(v);
button_delete.hidden(v);
button_cut.hidden(v);
button_copy.hidden(v);
button_paste.hidden(v);
button_new_dir.hidden(v);
button_new_file.hidden(v);
set_dirty();
}
FileManagerView::FileManagerView(
NavigationView& nav)
: FileManBaseView(nav, "") {
@@ -510,22 +555,33 @@ FileManagerView::FileManagerView(
refresh_widgets(v);
};
add_children({&menu_view,
&labels,
&text_date,
&button_rename,
&button_delete,
&button_cut,
&button_copy,
&button_paste,
&button_new_dir,
&button_new_file});
add_children({
&menu_view,
&text_date,
&button_rename,
&button_delete,
&button_cut,
&button_copy,
&button_paste,
&button_new_dir,
&button_new_file,
&button_open_notepad,
&button_rename_timestamp,
&button_open_iq_trim,
&button_show_hidden_files,
});
menu_view.on_highlight = [this]() {
if (selected_is_valid())
text_date.set(to_string_FAT_timestamp(file_created_date(get_selected_full_path())));
else
text_date.set("");
if (menu_view.highlighted_index() >= max_items_shown - 1) {
text_date.set_style(&Styles::red);
text_date.set("Too many files!");
} else {
text_date.set_style(&Styles::grey);
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
text_date.set("");
}
};
refresh_list();
@@ -533,6 +589,8 @@ FileManagerView::FileManagerView(
on_select_entry = [this](KeyEvent key) {
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
push_dir(get_selected_entry().path);
} else if (key == KeyEvent::Select && handle_file_open()) {
return;
} else {
button_rename.focus();
}
@@ -540,7 +598,7 @@ FileManagerView::FileManagerView(
button_rename.on_select = [this]() {
if (selected_is_valid())
on_rename();
on_rename("");
};
button_delete.on_select = [this]() {
@@ -578,6 +636,35 @@ FileManagerView::FileManagerView(
button_new_file.on_select = [this]() {
on_new_file();
};
button_open_notepad.on_select = [this]() {
if (selected_is_valid() && !get_selected_entry().is_directory) {
auto path = get_selected_full_path();
nav_.push<TextEditorView>(path);
} else
nav_.display_modal("Open in Notepad", "Can't open that in Notepad.");
};
button_rename_timestamp.on_select = [this]() {
if (selected_is_valid() && !get_selected_entry().is_directory) {
on_rename(::truncate(to_string_timestamp(rtc_time::now()), 8));
} else
nav_.display_modal("Timestamp Rename", "Can't rename that.");
};
button_open_iq_trim.on_select = [this]() {
auto path = get_selected_full_path();
if (selected_is_valid() && !get_selected_entry().is_directory && is_cxx_capture_file(path)) {
nav_.push<IQTrimView>(path);
} else
nav_.display_modal("IQ Trim", "Not a capture file.");
};
button_show_hidden_files.on_select = [this]() {
show_hidden_files = !show_hidden_files;
button_show_hidden_files.set_color(show_hidden_files ? Color::green() : Color::dark_grey());
reload_current();
};
}
} // namespace ui
+35 -12
View File
@@ -77,7 +77,9 @@ class FileManBaseView : public View {
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
{u"", &bitmap_icon_file, ui::Color::light_grey()} // NB: Must be last.
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
{u".REM", &bitmap_icon_remote, ui::Color::orange()}, // Remote
{u"", &bitmap_icon_file, ui::Color::light_grey()} // NB: Must be last.
};
std::filesystem::path get_selected_full_path() const;
@@ -102,6 +104,8 @@ class FileManBaseView : public View {
std::vector<fileman_entry> entry_list{};
std::vector<uint32_t> saved_index_stack{};
bool show_hidden_files{false};
Labels labels{
{{0, 0}, "Path:", Color::light_grey()}};
@@ -114,13 +118,8 @@ class FileManBaseView : public View {
{0, 2 * 8, 240, 26 * 8},
true};
// HACK: for item count limit.
Text text_info{
{1 * 8, 35 * 8, 15 * 8, 16},
""};
Button button_exit{
{21 * 8, 34 * 8, 9 * 8, 32},
{22 * 8, 34 * 8, 8 * 8, 32},
"Exit"};
};
@@ -206,20 +205,19 @@ class FileManagerView : public FileManBaseView {
ClipboardMode clipboard_mode{ClipboardMode::None};
void refresh_widgets(const bool v);
void on_rename();
void on_rename(std::string_view hint);
void on_delete();
void on_paste();
void on_new_dir();
void on_new_file();
bool handle_file_open();
// True if the selected entry is a real file item.
bool selected_is_valid() const;
Labels labels{
{{0, 26 * 8}, "Created ", Color::light_grey()}};
Text text_date{
{8 * 8, 26 * 8, 19 * 8, 16},
{0 * 8, 26 * 8, 28 * 8, 16},
""};
NewButton button_rename{
@@ -263,6 +261,31 @@ class FileManagerView : public FileManBaseView {
{},
&bitmap_icon_new_file,
Color::green()};
NewButton button_open_notepad{
{0 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_notepad,
Color::orange()};
NewButton button_rename_timestamp{
{4 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_options_datetime,
Color::orange(),
/*vcenter*/ true};
NewButton button_open_iq_trim{
{9 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_trim,
Color::orange()};
NewButton button_show_hidden_files{
{13 * 8, 34 * 8, 4 * 8, 32},
{},
&bitmap_icon_hide,
Color::dark_grey()};
};
} /* namespace ui */
+53 -2
View File
@@ -25,6 +25,8 @@
namespace ui {
static const char16_t* firmware_folder = u"/FIRMWARE";
Thread* FlashUtilityView::thread{nullptr};
static constexpr size_t max_filename_length = 26;
@@ -35,7 +37,9 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
for (const auto& entry : std::filesystem::directory_iterator(u"FIRMWARE", u"*.bin")) {
ensure_directory(firmware_folder);
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.bin")) {
auto filename = entry.path().filename();
auto path = entry.path().native();
@@ -46,6 +50,17 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
this->firmware_selected(path);
}});
}
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.tar")) {
auto filename = entry.path().filename();
auto path = entry.path().native();
menu_view.add_item({filename.string().substr(0, max_filename_length),
ui::Color::purple(),
&bitmap_icon_temperature,
[this, path](KeyEvent) {
this->firmware_selected(path);
}});
}
}
void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path) {
@@ -61,7 +76,43 @@ void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path
});
}
bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string& suffix) {
if (str.length() >= suffix.length()) {
std::u16string endOfString = str.substr(str.length() - suffix.length());
return endOfString == suffix;
} else {
return false;
}
}
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path) {
//
ui::Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::black());
painter.draw_string({12, 24}, this->nav_.style(), "Unpacking TAR file...");
auto res = UnTar::untar(path, [this](const std::string fileName) {
ui::Painter painter;
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
painter.draw_string({0, 60}, this->nav_.style(), fileName);
});
if (res.string().empty()) {
ui::Painter painter;
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
painter.draw_string({0, 60}, this->nav_.style(), "BAD TAR FILE");
chThdSleepMilliseconds(5000);
}
return res;
}
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
if (endsWith(path, u".tar")) {
// extract, then update
path = extract_tar(u'/' + path).native();
if (path.empty()) return;
}
ui::Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
@@ -70,7 +121,7 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15 seconds.");
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LEDs RX");
painter.draw_string({12, 84}, this->nav_.style(), "and TX are flashing.");
painter.draw_string({12, 124}, this->nav_.style(), "Then restart the device.");
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
@@ -29,7 +29,7 @@
#include "ff.h"
#include "baseband_api.hpp"
#include "core_control.hpp"
#include "untar.hpp"
#include <cstdint>
namespace ui {
@@ -55,8 +55,10 @@ class FlashUtilityView : public View {
{0, 2 * 8, 240, 26 * 8},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
void firmware_selected(std::filesystem::path::string_type path);
void flash_firmware(std::filesystem::path::string_type path);
bool endsWith(const std::u16string& str, const std::u16string& suffix);
};
} /* namespace ui */
+365 -225
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -22,312 +23,451 @@
#include "ui_freqman.hpp"
#include "portapack.hpp"
#include "binder.hpp"
#include "event_m0.hpp"
#include "portapack.hpp"
#include "rtc_time.hpp"
#include "tone_key.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "utility.hpp"
#include <memory>
using namespace portapack;
using namespace ui;
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;
/* Set options. */
void freqman_set_modulation_option(OptionsField& option) {
option.set_options(freqman_modulations);
}
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
if (is_valid(modulation))
option.set_options(freqman_bandwidths[modulation]);
}
void freqman_set_step_option(OptionsField& option) {
option.set_options(freqman_steps);
}
void freqman_set_step_option_short(OptionsField& option) {
option.set_options(freqman_steps_short);
}
namespace ui {
static int32_t last_category_id{0};
/* FreqManBaseView ***************************************/
size_t FreqManBaseView::current_category_index = 0;
FreqManBaseView::FreqManBaseView(
NavigationView& nav)
: nav_(nav) {
file_list = get_freqman_files();
add_children(
{&label_category,
&options_category,
&button_exit});
add_children({&label_category,
&button_exit});
if (file_list.size()) {
add_child(&options_category);
populate_categories();
} else
error_ = ERROR_NOFILES;
// initialize
change_category(last_category_id);
// Default function
on_change_category = [this](int32_t category_id) {
change_category(category_id);
options_category.on_change = [this](size_t new_index, int32_t) {
change_category(new_index);
};
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
};
refresh_categories();
};
void FreqManBaseView::focus() {
button_exit.focus();
// TODO: Shouldn't be on focus.
if (error_ == ERROR_ACCESS) {
nav_.display_modal("Error", "File acces error", ABORT, nullptr);
nav_.display_modal("Error", "File access error", ABORT);
} else if (error_ == ERROR_NOFILES) {
nav_.display_modal("Error", "No database files\nin /freqman", ABORT, nullptr);
nav_.display_modal("Error", "No database files\nin /FREQMAN", ABORT);
} else {
options_category.focus();
}
}
void FreqManBaseView::populate_categories() {
categories.clear();
void FreqManBaseView::change_category(size_t new_index) {
if (categories().empty())
return;
for (size_t n = 0; n < file_list.size(); n++)
categories.emplace_back(std::make_pair(file_list[n].substr(0, 14), n));
current_category_index = new_index;
if (!db_.open(get_freqman_path(current_category()))) {
error_ = ERROR_ACCESS;
}
// Alphabetical sort
std::sort(categories.begin(), categories.end(), [](auto& left, auto& right) {
freqlist_view.set_db(db_);
}
void FreqManBaseView::refresh_categories() {
OptionsField::options_t new_categories;
scan_root_files(
freqman_dir, u"*.TXT", [&new_categories](const fs::path& path) {
// Skip temp/hidden files.
if (path.empty() || path.native()[0] == u'.')
return;
// The UI layer will truncate long file names when displaying.
new_categories.emplace_back(path.stem().string(), new_categories.size());
});
// Alphabetically sort the categories.
std::sort(new_categories.begin(), new_categories.end(), [](auto& left, auto& right) {
return left.first < right.first;
});
options_category.set_options(categories);
options_category.set_selected_index(last_category_id);
options_category.on_change = [this](size_t category_id, int32_t) {
if (on_change_category)
on_change_category(category_id);
};
// Preserve last selection; ensure in range.
current_category_index = clip(current_category_index, 0u, new_categories.size());
auto saved_index = current_category_index;
options_category.set_options(std::move(new_categories));
options_category.set_selected_index(saved_index);
}
void FreqManBaseView::change_category(int32_t category_id) {
if (!file_list.size()) return;
last_category_id = current_category_id = category_id;
if (!load_freqman_file(file_list[categories[current_category_id].second], database))
error_ = ERROR_ACCESS;
else
refresh_list();
void FreqManBaseView::refresh_list(int delta_selected) {
// Update the index and ensures in bounds.
freqlist_view.set_index(freqlist_view.get_index() + delta_selected);
freqlist_view.set_dirty();
}
void FreqManBaseView::refresh_list() {
if (!database.size()) {
if (on_refresh_widgets)
on_refresh_widgets(true);
} else {
if (on_refresh_widgets)
on_refresh_widgets(false);
menu_view.clear();
for (size_t n = 0; n < database.size(); n++) {
menu_view.add_item({freqman_item_string(database[n], 30),
ui::Color::white(),
nullptr,
[this](KeyEvent) {
if (on_select_frequency)
on_select_frequency();
}});
}
menu_view.set_highlighted(0); // Refresh
}
}
void FrequencySaveView::save_current_file() {
if (database.size() > FREQMAN_MAX_PER_FILE) {
nav_.display_modal(
"Error", "Too many entries, maximum is\n" FREQMAN_MAX_PER_FILE_STR ". Trim list ?",
YESNO,
[this](bool choice) {
if (choice) {
database.resize(FREQMAN_MAX_PER_FILE);
save_freqman_file(file_list[categories[current_category_id].second], database);
}
nav_.pop();
});
} else {
save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.pop();
}
}
void FrequencySaveView::on_save_name() {
text_prompt(nav_, desc_buffer, 28, [this](std::string& buffer) {
database.push_back({value_, 0, buffer, SINGLE});
save_current_file();
});
}
void FrequencySaveView::on_save_timestamp() {
database.push_back({value_, 0, live_timestamp.string(), SINGLE});
save_current_file();
}
/* FrequencySaveView *************************************/
FrequencySaveView::FrequencySaveView(
NavigationView& nav,
const rf::Frequency value)
: FreqManBaseView(nav),
value_(value) {
desc_buffer.reserve(28);
: FreqManBaseView(nav) {
add_children(
{&labels,
&big_display,
&button_save,
&field_description});
// Todo: add back ?
/*for (size_t n = 0; n < database.size(); n++) {
if (database[n].value == value_) {
error_ = ERROR_DUPLICATE;
break;
}
}*/
entry_.type = freqman_type::Single;
entry_.frequency_a = value;
entry_.description = to_string_timestamp(rtc_time::now());
add_children({&labels,
&big_display,
&button_save_name,
&button_save_timestamp,
&live_timestamp});
bind(field_description, entry_.description, nav);
big_display.set(value);
button_save_name.on_select = [this, &nav](Button&) {
on_save_name();
};
button_save_timestamp.on_select = [this, &nav](Button&) {
on_save_timestamp();
button_save.on_select = [this, &nav](Button&) {
db_.insert_entry(db_.entry_count(), entry_);
nav_.pop();
};
}
void FrequencyLoadView::refresh_widgets(const bool v) {
menu_view.hidden(v);
text_empty.hidden(!v);
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
set_dirty();
void FrequencySaveView::focus() {
refresh_ui();
FreqManBaseView::focus();
}
void FrequencySaveView::refresh_ui() {
big_display.set(entry_.frequency_a);
}
/* FrequencyLoadView *************************************/
FrequencyLoadView::FrequencyLoadView(
NavigationView& nav)
: FreqManBaseView(nav) {
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
add_children({&freqlist_view});
// Resize to fill screen. +2 keeps text out of border.
freqlist_view.set_parent_rect({0, 3 * 8, screen_width, 15 * 16 + 2});
freqlist_view.on_select = [&nav, this](size_t index) {
auto entry = db_[index];
// TODO: Maybe return center of range if user choses a range when the app
// needs a unique frequency, instead of frequency_a?
auto has_range = entry.type == freqman_type::Range ||
entry.type == freqman_type::HamRadio;
if (on_range_loaded && has_range)
on_range_loaded(entry.frequency_a, entry.frequency_b);
else if (on_frequency_loaded)
on_frequency_loaded(entry.frequency_a);
nav_.pop(); // NB: this will call dtor.
};
add_children({&menu_view,
&text_empty});
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 30 * 8});
// Just to allow exit on left
menu_view.on_left = [&nav, this]() {
nav.pop();
freqlist_view.on_leave = [this]() {
button_exit.focus();
};
}
change_category(last_category_id);
refresh_list();
/* FrequencyManagerView **********************************/
on_select_frequency = [&nav, this]() {
nav_.pop();
void FrequencyManagerView::on_edit_entry() {
auto edit_view = nav_.push<FrequencyEditView>(current_entry());
edit_view->on_save = [this](const freqman_entry& entry) {
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
};
}
auto& entry = database[menu_view.highlighted_index()];
void FrequencyManagerView::on_edit_freq() {
auto freq_edit_view = nav_.push<FrequencyKeypadView>(current_entry().frequency_a);
freq_edit_view->on_changed = [this](rf::Frequency f) {
auto entry = current_entry();
entry.frequency_a = f;
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
};
}
if (entry.type == RANGE) {
// User chose a frequency range entry
if (on_range_loaded)
on_range_loaded(entry.frequency_a, entry.frequency_b);
else if (on_frequency_loaded)
on_frequency_loaded(entry.frequency_a);
// TODO: Maybe return center of range if user choses a range when the app needs a unique frequency, instead of frequency_a ?
} else {
// User chose an unique frequency entry
if (on_frequency_loaded)
on_frequency_loaded(entry.frequency_a);
void FrequencyManagerView::on_edit_desc() {
temp_buffer_ = current_entry().description;
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
auto entry = current_entry();
entry.description = std::move(new_desc);
db_.replace_entry(current_index(), entry);
freqlist_view.set_dirty();
});
}
void FrequencyManagerView::on_add_category() {
temp_buffer_.clear();
text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
if (!new_name.empty()) {
create_freqman_file(new_name);
refresh_categories();
}
});
}
void FrequencyManagerView::on_del_category() {
nav_.push<ModalMessageView>(
"Delete", "Delete " + current_category() + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
db_.close(); // Ensure file is closed.
auto path = get_freqman_path(current_category());
delete_file(path);
refresh_categories();
}
});
}
void FrequencyManagerView::on_add_entry() {
freqman_entry entry{
.frequency_a = 100'000'000,
.description = std::string{"Entry "} + to_string_dec_uint(db_.entry_count()),
.type = freqman_type::Single,
};
// Add will insert below the currently selected item.
db_.insert_entry(current_index() + 1, entry);
refresh_list(1);
}
void FrequencyManagerView::on_edit_freq(rf::Frequency f) {
database[menu_view.highlighted_index()].frequency_a = f;
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
}
void FrequencyManagerView::on_del_entry() {
if (db_.empty())
return;
void FrequencyManagerView::on_edit_desc(NavigationView& nav) {
text_prompt(nav, desc_buffer, 28, [this](std::string& buffer) {
database[menu_view.highlighted_index()].description = buffer;
refresh_list();
save_freqman_file(file_list[categories[current_category_id].second], database);
});
}
void FrequencyManagerView::on_new_category(NavigationView& nav) {
text_prompt(nav, desc_buffer, 12, [this](std::string& buffer) {
File freqman_file;
create_freqman_file(buffer, freqman_file);
});
populate_categories();
refresh_list();
}
void FrequencyManagerView::on_delete() {
database.erase(database.begin() + menu_view.highlighted_index());
save_freqman_file(file_list[categories[current_category_id].second], database);
refresh_list();
}
void FrequencyManagerView::refresh_widgets(const bool v) {
button_edit_freq.hidden(v);
button_edit_desc.hidden(v);
button_delete.hidden(v);
menu_view.hidden(v);
text_empty.hidden(!v);
// display.fill_rectangle(menu_view.screen_rect(), Color::black());
set_dirty();
}
FrequencyManagerView::~FrequencyManagerView() {
// save_freqman_file(file_list[categories[current_category_id].second], database);
nav_.push<ModalMessageView>(
"Delete", "Delete " + trim(pretty_string(current_entry(), 23)) + "\nAre you sure?", YESNO,
[this](bool choice) {
if (choice) {
db_.delete_entry(current_index());
refresh_list();
}
});
}
FrequencyManagerView::FrequencyManagerView(
NavigationView& nav)
: FreqManBaseView(nav) {
on_refresh_widgets = [this](bool v) {
refresh_widgets(v);
add_children(
{&freqlist_view,
&button_add_category,
&button_del_category,
&button_edit_entry,
&rect_padding,
&button_edit_freq,
&button_edit_desc,
&button_add_entry,
&button_del_entry});
freqlist_view.on_select = [this](size_t) {
button_edit_entry.focus();
};
// Allows for quickly exiting control.
freqlist_view.on_leave = [this]() {
button_edit_entry.focus();
};
add_children({&labels,
&button_new_category,
&menu_view,
&text_empty,
&button_edit_freq,
&button_edit_desc,
&button_delete});
// Just to allow exit on left
menu_view.on_left = [&nav, this]() {
nav.pop();
button_add_category.on_select = [this]() {
on_add_category();
};
change_category(last_category_id);
refresh_list();
on_select_frequency = [this]() {
button_edit_freq.focus();
button_del_category.on_select = [this]() {
on_del_category();
};
button_new_category.on_select = [this, &nav](Button&) {
desc_buffer = "";
on_new_category(nav);
button_edit_entry.on_select = [this](Button&) {
on_edit_entry();
};
button_edit_freq.on_select = [this, &nav](Button&) {
auto new_view = nav.push<FrequencyKeypadView>(database[menu_view.highlighted_index()].frequency_a);
new_view->on_changed = [this](rf::Frequency f) {
on_edit_freq(f);
};
button_edit_freq.on_select = [this](Button&) {
on_edit_freq();
};
button_edit_desc.on_select = [this, &nav](Button&) {
desc_buffer = database[menu_view.highlighted_index()].description;
on_edit_desc(nav);
button_edit_desc.on_select = [this](Button&) {
on_edit_desc();
};
button_delete.on_select = [this, &nav](Button&) {
nav.push<ModalMessageView>("Confirm", "Are you sure ?", YESNO,
[this](bool choice) {
if (choice)
on_delete();
});
button_add_entry.on_select = [this]() {
on_add_entry();
};
button_del_entry.on_select = [this]() {
on_del_entry();
};
}
/* FrequencyEditView *************************************/
FrequencyEditView::FrequencyEditView(
NavigationView& nav,
freqman_entry entry)
: nav_{nav},
entry_{std::move(entry)} {
add_children({&labels,
&field_type,
&field_freq_a,
&field_freq_b,
&field_modulation,
&field_bandwidth,
&field_step,
&field_tone,
&field_description,
&text_validation,
&button_save,
&button_cancel});
freqman_set_modulation_option(field_modulation);
populate_bandwidth_options();
populate_step_options();
populate_tone_options();
// Add the "invalid/unset" option. Kind of hacky, but...
field_modulation.options().insert(
field_modulation.options().begin(), {"None", -1});
field_step.options().insert(
field_step.options().begin(), {"None", -1});
bind(field_type, entry_.type, [this](auto) {
refresh_ui();
});
bind(field_freq_a, entry_.frequency_a, nav, [this](auto) {
refresh_ui();
});
bind(field_freq_b, entry_.frequency_b, nav, [this](auto) {
refresh_ui();
});
bind(field_modulation, entry_.modulation, [this](auto) {
populate_bandwidth_options();
});
bind(field_bandwidth, entry_.bandwidth);
bind(field_step, entry_.step);
bind(field_tone, entry_.tone);
bind(field_description, entry_.description, nav_);
button_save.on_select = [this](Button&) {
if (on_save)
on_save(std::move(entry_));
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
nav_.pop();
};
refresh_ui();
}
void FrequencyEditView::focus() {
button_cancel.focus();
}
void FrequencyEditView::refresh_ui() {
// This needs to be called whenever a UI option is changed
// in a way that causes fields to be unused or would make the
// entry fail validation.
auto is_range = entry_.type == freqman_type::Range;
auto is_ham = entry_.type == freqman_type::HamRadio;
auto is_repeater = entry_.type == freqman_type::Repeater;
auto has_freq_b = is_range || is_ham || is_repeater;
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
field_tone.set_style(is_ham ? &Styles::white : &Styles::grey);
if (is_valid(entry_)) {
text_validation.set("Valid");
text_validation.set_style(&Styles::green);
} else {
text_validation.set("Error");
text_validation.set_style(&Styles::red);
}
}
// TODO: The following functions shouldn't be needed once
// freqman_db lookup tables are complete.
void FrequencyEditView::populate_bandwidth_options() {
OptionsField::options_t options;
options.push_back({"None", -1});
if (entry_.modulation < std::size(freqman_bandwidths)) {
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});
}
}
field_bandwidth.set_options(std::move(options));
}
void FrequencyEditView::populate_step_options() {
OptionsField::options_t options;
options.push_back({"None", -1});
for (auto i = 0u; i < freqman_steps.size(); ++i) {
auto& item = freqman_steps[i];
options.push_back({item.first, (OptionsField::value_t)i});
}
field_step.set_options(std::move(options));
}
void FrequencyEditView::populate_tone_options() {
using namespace tonekey;
OptionsField::options_t options;
options.push_back({"None", -1});
for (auto i = 0u; i < tone_keys.size(); ++i) {
auto& item = tone_keys[i];
options.push_back({fx100_string(item.second), (OptionsField::value_t)i});
}
field_tone.set_options(std::move(options));
}
} // namespace ui
+171 -69
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
@@ -20,93 +21,93 @@
* Boston, MA 02110-1301, USA.
*/
#include "freqman.hpp"
#include "freqman_db.hpp"
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "ui_freqlist.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
#include "ui_painter.hpp"
#include "ui_receiver.hpp"
#include "ui_textentry.hpp"
#include "freqman.hpp"
#include "ui_widget.hpp"
namespace ui {
class FreqManBaseView : public View {
public:
FreqManBaseView(
NavigationView& nav);
FreqManBaseView(NavigationView& nav);
void focus() override;
static constexpr size_t desc_edit_max = 0x80;
protected:
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
using options_t = OptionsField::options_t;
NavigationView& nav_;
freqman_error error_{NO_ERROR};
options_t categories{};
std::function<void(int32_t category_id)> on_change_category{nullptr};
std::function<void(void)> on_select_frequency{nullptr};
std::function<void(bool)> on_refresh_widgets{nullptr};
std::vector<std::string> file_list{};
int32_t current_category_id{0};
std::function<void(void)> on_select_frequency{};
void populate_categories();
void change_category(int32_t category_id);
void refresh_list();
void change_category(size_t new_index);
/* Access the categories directly from the OptionsField.
* This avoids holding multiple copies of the file list. */
const options_t& categories() const { return options_category.options(); }
const auto& current_category() const { return options_category.selected_index_name(); }
auto current_index() const { return freqlist_view.get_index(); }
freqman_entry current_entry() const { return db_[current_index()]; }
void refresh_categories();
void refresh_list(int delta_selected = 0);
freqman_db database{};
FreqmanDB db_{};
/* The top section (category) is 20px tall. */
Labels label_category{
{{0, 4}, "Category:", Color::light_grey()}};
{{0, 2}, "Category:", Color::light_grey()}};
OptionsField options_category{
{9 * 8, 4},
14,
{9 * 8, 2},
14 /* length */,
{}};
MenuView menu_view{
{0, 3 * 8, 240, 23 * 8},
true};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
"Empty category !",
};
FreqManUIList freqlist_view{
{0, 3 * 8, screen_width, 12 * 16 + 2 /* 2 Keeps text out of border. */}};
Button button_exit{
{20 * 8, 34 * 8, 10 * 8, 4 * 8},
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
"Exit"};
protected:
/* Static so selected category is persisted across UI instances. */
static size_t current_category_index;
};
// TODO: support for new category.
class FrequencySaveView : public FreqManBaseView {
public:
FrequencySaveView(NavigationView& nav, const rf::Frequency value);
std::string title() const override { return "Save freq."; };
std::string title() const override { return "Save freq"; }
void focus() override;
private:
std::string desc_buffer{};
rf::Frequency value_{};
freqman_entry entry_{};
void on_save_name();
void on_save_timestamp();
void save_current_file();
void refresh_ui();
BigFrequency big_display{
{4, 2 * 16, 28 * 8, 32},
{0, 2 * 16, 28 * 8, 4 * 16},
0};
Labels labels{
{{1 * 8, 12 * 8}, "Save as:", Color::white()}};
{{0 * 8, 6 * 16}, "Description:", Color::white()}};
Button button_save_name{
{1 * 8, 17 * 8, 12 * 8, 48},
"Name (set)"};
Button button_save_timestamp{
{1 * 8, 25 * 8, 12 * 8, 48},
"Timestamp:"};
LiveDateTime live_timestamp{
{14 * 8, 27 * 8, 16 * 8, 16}};
TextField field_description{
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
""};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Save"};
};
class FrequencyLoadView : public FreqManBaseView {
@@ -115,46 +116,147 @@ class FrequencyLoadView : public FreqManBaseView {
std::function<void(rf::Frequency, rf::Frequency)> on_range_loaded{};
FrequencyLoadView(NavigationView& nav);
std::string title() const override { return "Load freq."; };
private:
void refresh_widgets(const bool v);
std::string title() const override { return "Load freq"; }
};
class FrequencyManagerView : public FreqManBaseView {
public:
FrequencyManagerView(NavigationView& nav);
~FrequencyManagerView();
std::string title() const override { return "Freqman"; };
std::string title() const override { return "Freqman"; }
private:
std::string desc_buffer{};
std::string temp_buffer_{};
void refresh_widgets(const bool v);
void on_edit_freq(rf::Frequency f);
void on_edit_desc(NavigationView& nav);
void on_new_category(NavigationView& nav);
void on_delete();
void on_edit_entry();
void on_edit_freq();
void on_edit_desc();
void on_add_category();
void on_del_category();
void on_add_entry();
void on_del_entry();
Labels labels{
{{4 * 8 + 4, 26 * 8}, "Edit:", Color::light_grey()}};
NewButton button_add_category{
{23 * 8, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_new_file,
Color::white(),
true};
Button button_new_category{
{23 * 8, 2, 7 * 8, 20},
"New"};
NewButton button_del_category{
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
{},
&bitmap_icon_trash,
Color::red(),
true};
Button button_edit_entry{
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
"Edit"};
Rectangle rect_padding{
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
Color::grey()};
Button button_edit_freq{
{0 * 8, 29 * 8, 14 * 8, 32},
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
"Frequency"};
Button button_edit_desc{
{0 * 8, 34 * 8, 14 * 8, 32},
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Description"};
Button button_delete{
{18 * 8, 27 * 8, 12 * 8, 32},
"Delete"};
NewButton button_add_entry{
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_add,
Color::white(),
true};
NewButton button_del_entry{
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_delete,
Color::red(),
true};
};
class FrequencyEditView : public View {
public:
std::function<void(const freqman_entry&)> on_save{};
FrequencyEditView(
NavigationView& nav,
freqman_entry entry);
std::string title() const override { return "Freqman Edit"; }
void focus() override;
private:
NavigationView& nav_;
freqman_entry entry_;
std::string temp_buffer_{};
void refresh_ui();
void populate_bandwidth_options();
void populate_step_options();
void populate_tone_options();
Labels labels{
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Color::white()},
{{0 * 8, 3 * 16}, "Entry Type :", Color::light_grey()},
{{0 * 8, 4 * 16}, "Frequency A:", Color::light_grey()},
{{0 * 8, 5 * 16}, "Frequency B:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Modulation :", Color::light_grey()},
{{0 * 8, 7 * 16}, "Bandwidth :", Color::light_grey()},
{{0 * 8, 8 * 16}, "Step :", Color::light_grey()},
{{0 * 8, 9 * 16}, "Tone Freq :", Color::light_grey()},
{{0 * 8, 10 * 16}, "Description:", Color::light_grey()},
};
OptionsField field_type{
{13 * 8, 3 * 16},
8,
{
{"Single", 0},
{"Range", 1},
{"HamRadio", 2},
{"Repeater", 3},
{"Raw", 4},
}};
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
FrequencyField field_freq_b{{13 * 8, 5 * 16}};
OptionsField field_modulation{{13 * 8, 6 * 16}, 5, {}};
OptionsField field_bandwidth{{13 * 8, 7 * 16}, 7, {}};
OptionsField field_step{{13 * 8, 8 * 16}, 12, {}};
OptionsField field_tone{{13 * 8, 9 * 16}, 13, {}};
TextField field_description{
{13 * 8, 10 * 16, 17 * 8, 1 * 16},
{}};
Text text_validation{
{12 * 8, 12 * 16, 5 * 8, 1 * 16},
{}};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
"Save"};
Button button_cancel{
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
"Cancel"};
};
} /* namespace ui */
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
void freqman_set_modulation_option(ui::OptionsField& option);
void freqman_set_step_option(ui::OptionsField& option);
void freqman_set_step_option_short(ui::OptionsField& option);
void freqman_set_tone_option(ui::OptionsField& option);
+197
View File
@@ -0,0 +1,197 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* 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_fsk_rx.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "utility.hpp"
#include "ui_freqman.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
void FskRxLogger::log_raw_data(const std::string& data, const uint32_t frequency) {
std::string entry = "Raw: F:" + to_string_dec_uint(frequency) + "Hz";
// // Raw hex dump of all the codewords
// for (size_t c = 0; c < 16; c++)
// entry += to_string_hex(packet[c], 8) + " ";
log_file.write_entry(data + entry);
}
void FskRxLogger::log_decoded(Timestamp timestamp, const std::string& text) {
log_file.write_entry(timestamp, text);
}
namespace ui {
//---------------------------------------------------------------------------------------------------------------
// Console View
//---------------------------------------------------------------------------------------------------------------
FskRxAppConsoleView::FskRxAppConsoleView(NavigationView& nav, Rect parent_rect)
: View(parent_rect), nav_{nav} {
add_child(&console);
};
void FskRxAppConsoleView::on_packet(uint32_t value, bool is_data) {
if (is_data) {
console.write(to_string_dec_uint(value) + " ");
}
}
void FskRxAppConsoleView::on_show() {
hidden(false);
}
void FskRxAppConsoleView::on_hide() {
hidden(true);
}
FskRxAppConsoleView::~FskRxAppConsoleView() {
}
//---------------------------------------------------------------------------------------------------------------
// Spectrum View
//---------------------------------------------------------------------------------------------------------------
FskRxAppView::FskRxAppView(NavigationView& nav, Rect parent_rect)
: View(parent_rect), nav_{nav} {
add_child(&waterfall);
hidden(true);
}
FskRxAppView::~FskRxAppView() {
}
void FskRxAppView::focus() {
}
void FskRxAppView::on_show() {
hidden(false);
waterfall.start();
}
void FskRxAppView::on_hide() {
hidden(true);
waterfall.stop();
}
//---------------------------------------------------------------------------------------------------------------
// Base View
//---------------------------------------------------------------------------------------------------------------
FskxRxMainView::FskxRxMainView(NavigationView& nav)
: nav_{nav} {
add_children({&tab_view,
&view_data,
&view_stream,
&labels,
&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&deviation_frequency,
&record_view});
baseband::run_image(portapack::spi_flash::image_tag_fskrx);
// DEBUG
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
deviation_frequency.on_change = [this](rf::Frequency f) {
refresh_ui(f);
};
// Set initial sampling rate
/* Bandwidth of 2FSK is 2 * Deviation */
record_view.set_sampling_rate(initial_deviation * 2);
field_frequency.set_value(initial_target_frequency);
deviation_frequency.set_value(initial_deviation);
logger.append(LOG_ROOT_DIR "/FSKRX.TXT");
baseband::set_fsk(initial_deviation);
audio::output::start();
receiver_model.enable();
}
void FskxRxMainView::handle_decoded(Timestamp timestamp, const std::string& prefix) {
if (logging()) {
logger.log_decoded(timestamp, prefix);
}
}
void FskxRxMainView::refresh_ui(rf::Frequency deviationHz) {
/* 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. */
/* Bandwidth of 2FSK is 2 * Deviation */
auto sample_rate = deviationHz * 2;
/* base_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. */
if (!view_stream.hidden()) {
view_stream.waterfall.stop();
}
// 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);
// Update the radio model with the actual sampling rate.
receiver_model.set_sampling_rate(actual_sample_rate);
// Get suitable anti-aliasing BPF bandwidth for MAX2837 given the actual sample rate.
auto anti_alias_filter_bandwidth = filter_bandwidth_for_sampling_rate(actual_sample_rate);
receiver_model.set_baseband_bandwidth(anti_alias_filter_bandwidth);
if (!view_stream.hidden()) {
view_stream.waterfall.start();
}
}
void FskxRxMainView::focus() {
field_frequency.focus();
}
void FskxRxMainView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
ui::Rect waterfall_rect{0, 0, new_parent_rect.width(), new_parent_rect.height() - header_height};
view_stream.waterfall.set_parent_rect(waterfall_rect);
}
FskxRxMainView::~FskxRxMainView() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
} /* namespace ui */
+178
View File
@@ -0,0 +1,178 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio 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 __UI_FSK_RX_H__
#define __UI_FSK_RX_H__
#include "ui_widget.hpp"
#include "ui_freq_field.hpp"
#include "ui_receiver.hpp"
#include "ui_record_view.hpp"
#include "ui_rssi.hpp"
#include "ui_spectrum.hpp"
#include "ui_styles.hpp"
#include "ui_tabview.hpp"
#include "app_settings.hpp"
#include "log_file.hpp"
#include "radio_state.hpp"
#include "pocsag_app.hpp"
#include <functional>
class FskRxLogger {
public:
Optional<File::Error> append(const std::string& filename) {
return log_file.append(filename);
}
void log_raw_data(const std::string& data, const uint32_t frequency);
void log_decoded(Timestamp timestamp, const std::string& text);
private:
LogFile log_file{};
};
namespace ui {
class FskRxAppConsoleView : public View {
public:
FskRxAppConsoleView(NavigationView& nav, Rect parent_rec);
~FskRxAppConsoleView();
std::string title() const override { return "FSK RX Data"; };
void on_packet(uint32_t value, bool is_data);
void on_show() override;
void on_hide() override;
private:
NavigationView& nav_;
Console console{
{0, 0, 240, 224}};
};
class FskRxAppView : public View {
public:
FskRxAppView(NavigationView& nav, Rect parent_rect);
~FskRxAppView();
void focus() override;
void on_show() override;
void on_hide() override;
spectrum::WaterfallView waterfall{};
std::string title() const override { return "FSK RX Stream"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
};
class FskxRxMainView : public View {
public:
FskxRxMainView(NavigationView& nav);
~FskxRxMainView();
void focus() override;
void set_parent_rect(const Rect new_parent_rect) override;
std::string title() const override { return "FSK RX"; };
private:
static constexpr uint32_t initial_target_frequency = 902'075'000;
static constexpr ui::Dim header_height = (5 * 16);
uint32_t initial_deviation{3750};
bool logging() const { return false; };
bool logging_raw() const { return false; };
NavigationView& nav_;
Rect view_rect = {0, header_height, 240, 224};
FskRxAppView view_stream{nav_, view_rect};
FskRxAppConsoleView view_data{nav_, view_rect};
TabView tab_view{
{"Data", Color::yellow(), &view_data},
{"Stream", Color::cyan(), &view_stream}};
void refresh_ui(rf::Frequency f);
void on_packet(uint32_t value, bool is_data);
void handle_decoded(Timestamp timestamp, const std::string& prefix);
uint32_t last_address = 0;
FskRxLogger logger{};
uint16_t packet_count = 0;
RxFrequencyField field_frequency{
{0 * 8, 4 * 8},
nav_};
RFAmpField field_rf_amp{
{11 * 8, 2 * 16}};
LNAGainField field_lna{
{13 * 8, 2 * 16}};
VGAGainField field_vga{
{16 * 8, 2 * 16}};
RSSI rssi{
{19 * 8 - 4, 35, 6 * 8, 4}};
Channel channel{
{19 * 8 - 4, 40, 6 * 8, 4}};
Labels labels{
{{0 * 8, 3 * 16}, "Deviation:", Color::light_grey()},
};
FrequencyField deviation_frequency{
{10 * 8, 3 * 16},
{3750, 500000},
};
// DEBUG
RecordView record_view{
{0 * 8, 4 * 16, 30 * 8, 1 * 16},
u"FSKRX_????.C16",
u"FSKRX",
RecordView::FileType::RawS16,
16384,
3};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(p);
this->view_data.on_packet(message->value, message->is_data);
}};
};
} /* namespace ui */
#endif /*__POCSAG_APP_H__*/
+220
View File
@@ -0,0 +1,220 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_iq_trim.hpp"
#include "complex.hpp"
#include "portapack.hpp"
#include "ui_fileman.hpp"
using namespace portapack;
namespace fs = std::filesystem;
namespace ui {
IQTrimView::IQTrimView(NavigationView& nav)
: nav_{nav} {
add_children({
&labels,
&field_path,
&field_start,
&field_end,
&text_samples,
&text_max,
&field_cutoff,
&field_amplify,
&button_trim,
});
field_path.on_select = [this](TextField&) {
auto open_view = nav_.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
open_file(path);
};
};
text_samples.set_style(&Styles::light_grey);
text_max.set_style(&Styles::light_grey);
field_start.on_change = [this](int32_t v) {
if (field_end.value() < v)
field_end.set_value(v, false);
set_dirty();
};
field_end.on_change = [this](int32_t v) {
if (field_start.value() > v)
field_start.set_value(v, false);
set_dirty();
};
field_cutoff.set_value(7); // 7% of max is a good default.
field_cutoff.on_change = [this](int32_t) {
compute_range();
refresh_ui();
};
field_amplify.set_value(1); // 1X is default (no amplification)
field_amplify.on_change = [this](int32_t) {
refresh_ui();
};
button_trim.on_select = [this](Button&) {
if (trim_capture()) {
profile_capture();
compute_range();
refresh_ui();
}
};
}
IQTrimView::IQTrimView(NavigationView& nav, const fs::path& path)
: IQTrimView(nav) {
open_file(path);
}
void IQTrimView::open_file(const std::filesystem::path& path) {
path_ = std::move(path);
profile_capture();
compute_range();
refresh_ui();
}
void IQTrimView::paint(Painter& painter) {
if (info_) {
uint32_t power_cutoff = field_cutoff.value() * static_cast<uint64_t>(info_->max_power) / 100;
// Draw power buckets.
for (size_t i = 0; i < power_buckets_.size(); ++i) {
auto power = power_buckets_[i].power;
uint8_t amp = 0;
if (power > power_cutoff && info_->max_power > 0)
amp = (255ULL * power) / info_->max_power;
painter.draw_vline(
pos_lines + Point{(int)i, 0},
height_lines,
Color(amp, amp, amp));
}
// Draw trim range edges.
int start_x = screen_width * field_start.value() / info_->sample_count;
int end_x = screen_width * field_end.value() / info_->sample_count;
painter.draw_vline(
pos_lines + Point{start_x, 0},
height_lines,
Color::dark_green());
painter.draw_vline(
pos_lines + Point{end_x, 0},
height_lines,
Color::dark_red());
}
}
void IQTrimView::focus() {
field_path.focus();
}
void IQTrimView::refresh_ui() {
field_path.set_text(path_.filename().string());
text_samples.set(to_string_dec_uint(info_->sample_count));
// show max power after amplification applied
uint64_t power_amp = field_amplify.value() * field_amplify.value() * field_amplify.value() * field_amplify.value();
text_max.set(to_string_dec_uint(info_->max_power * power_amp));
// show max power in red if amplification is too high, causing clipping
uint32_t clipping_limit = (fs::capture_file_sample_size(path_) == sizeof(complex8_t)) ? 0x80 : 0x8000;
if ((field_amplify.value() * info_->max_iq) > clipping_limit)
text_max.set_style(&Styles::red);
else
text_max.set_style(&Styles::light_grey);
set_dirty();
}
void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
auto max_range = info_ ? info_->sample_count : 0;
auto step = info_ ? info_->sample_count / screen_width : 0;
field_start.set_range(0, max_range);
field_start.set_step(step);
field_end.set_range(0, max_range);
field_end.set_step(step);
field_start.set_value(trim_range.start_sample, false);
field_end.set_value(trim_range.end_sample, false);
}
void IQTrimView::profile_capture() {
power_buckets_ = {};
iq::PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
progress_ui.show_reading();
info_ = iq::profile_capture(path_, buckets);
progress_ui.clear();
}
void IQTrimView::compute_range() {
if (!info_)
return;
iq::PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
auto trim_range = iq::compute_trim_range(*info_, buckets, field_cutoff.value());
update_range_controls(trim_range);
}
bool IQTrimView::trim_capture() {
if (!info_) {
nav_.display_modal("Error", "Open a file first.");
return false;
}
bool trimmed = false;
iq::TrimRange trim_range{
static_cast<uint32_t>(field_start.value()),
static_cast<uint32_t>(field_end.value()),
info_->sample_size};
if (trim_range.start_sample >= trim_range.end_sample) {
nav_.display_modal("Error", "Invalid trimming range.");
return false;
}
progress_ui.show_trimming();
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback(), field_amplify.value());
progress_ui.clear();
if (!trimmed)
nav_.display_modal("Error", "Trimming failed.");
return trimmed;
}
} // namespace ui
+164
View File
@@ -0,0 +1,164 @@
/*
* Copyright (C) 2023 Kyle Reed
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_IQ_TRIM_H__
#define __UI_IQ_TRIM_H__
#include "file.hpp"
#include "iq_trim.hpp"
#include "optional.hpp"
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_styles.hpp"
#include "ui_widget.hpp"
#include <array>
namespace ui {
/* Helper for drawing progress related to IQ trimming. */
/* TODO: Ideally this would all be done on second thread. */
class TrimProgressUI {
public:
void show_reading() {
clear();
p.draw_string({6 * 8, 5 * 16}, Styles::yellow, "Reading Capture...");
}
void show_trimming() {
clear();
p.draw_string({5 * 8, 5 * 16}, Styles::yellow, "Trimming Capture...");
}
void show_progress(uint8_t percent) {
auto width = percent * screen_width / 100;
p.draw_hline({0, 6 * 16 + 2}, width, Color::yellow());
}
void clear() {
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Color::black());
}
auto get_callback() {
return [this](uint8_t percent) { show_progress(percent); };
}
private:
Painter p{};
};
class IQTrimView : public View {
public:
IQTrimView(NavigationView& nav);
IQTrimView(NavigationView& nav, const std::filesystem::path& path);
std::string title() const override { return "IQ Trim"; }
void paint(Painter& painter) override;
void focus() override;
private:
void open_file(const std::filesystem::path& path);
/* Update controls with latest values. */
void refresh_ui();
/* Update the start/end controls with trim range info. */
void update_range_controls(iq::TrimRange trim_range);
/* Collect capture info and samples to draw the UI. */
void profile_capture();
/* Determine the start and end buckets based on the cutoff. */
void compute_range();
/* Trims the capture file based on the settings. */
bool trim_capture();
NavigationView& nav_;
std::filesystem::path path_{};
Optional<iq::CaptureInfo> info_{};
std::array<iq::PowerBuckets::Bucket, screen_width> power_buckets_{};
TrimProgressUI progress_ui{};
Labels labels{
{{0 * 8, 0 * 16}, "Capture File:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Start :", Color::light_grey()},
{{0 * 8, 7 * 16}, "End :", Color::light_grey()},
{{0 * 8, 8 * 16}, "Samples:", Color::light_grey()},
{{0 * 8, 9 * 16}, "Max Pwr:", Color::light_grey()},
{{0 * 8, 10 * 16}, "Cutoff :", Color::light_grey()},
{{12 * 8, 10 * 16}, "%", Color::light_grey()},
{{0 * 8, 12 * 16}, "Amplify:", Color::light_grey()},
{{10 * 8, 12 * 16}, "x", Color::light_grey()},
};
TextField field_path{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
Dim height_lines{2 * 16};
NumberField field_start{
{9 * 8, 6 * 16},
10,
{0, 0},
1,
' '};
NumberField field_end{
{9 * 8, 7 * 16},
10,
{0, 0},
1,
' '};
Text text_samples{
{9 * 8, 8 * 16, 10 * 8, 1 * 16},
"0"};
Text text_max{
{9 * 8, 9 * 16, 20 * 8, 1 * 16},
"0"};
NumberField field_cutoff{
{9 * 8, 10 * 16},
3,
{1, 100},
1,
' '};
NumberField field_amplify{
{9 * 8, 12 * 16},
1,
{1, 9},
1,
' '};
Button button_trim{
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
"Trim"};
};
} /* namespace ui */
#endif /*__UI_IQ_TRIM_H__*/
+68 -118
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -22,29 +23,22 @@
#include "ui_level.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "oversample.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace ui {
bool LevelView::check_sd_card() {
return (sd_card::status() == sd_card::Status::Mounted) ? true : false;
}
void LevelView::focus() {
button_frequency.focus();
}
LevelView::~LevelView() {
// save app settings
app_settings.lna = field_lna.value();
app_settings.vga = field_vga.value();
app_settings.rx_amp = field_rf_amp.value();
settings.save("level", &app_settings);
receiver_model.disable();
baseband::shutdown();
}
@@ -71,56 +65,34 @@ LevelView::LevelView(NavigationView& nav)
rssi.set_vertical_rssi(true);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
// Level directory
if (check_sd_card()) { // Check to see if SD Card is mounted
make_new_directory(u"/LEVEL");
sd_card_mounted = true;
}
change_mode(NFM_MODULATION); // Start on AM
field_mode.set_by_value(NFM_MODULATION); // Reflect the mode into the manual selector
// HELPER: Pre-setting a manual range, based on stored frequency
freq = persistent_memory::tuned_frequency();
receiver_model.set_tuning_frequency(freq);
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
// load auto common app settings
if (sd_card_mounted) {
auto rc = settings.load("level", &app_settings);
if (rc == SETTINGS_OK) {
field_lna.set_value(app_settings.lna);
field_vga.set_value(app_settings.vga);
field_rf_amp.set_value(app_settings.rx_amp);
receiver_model.set_rf_amp(app_settings.rx_amp);
}
}
freq_ = receiver_model.target_frequency();
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
button_frequency.on_select = [this, &nav](ButtonWithEncoder& button) {
auto new_view = nav_.push<FrequencyKeypadView>(freq);
auto new_view = nav_.push<FrequencyKeypadView>(freq_);
new_view->on_changed = [this, &button](rf::Frequency f) {
freq = f;
receiver_model.set_tuning_frequency(f); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
freq_ = f;
receiver_model.set_target_frequency(f); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
};
};
button_frequency.on_change = [this]() {
int64_t def_step = freqman_entry_get_step_value(step_mode.selected_index());
freq = freq + (button_frequency.get_encoder_delta() * def_step);
if (freq < 1) {
freq = 1;
freq_ = freq_ + (button_frequency.get_encoder_delta() * def_step);
if (freq_ < 1) {
freq_ = 1;
}
if (freq > (MAX_UFREQ - def_step)) {
freq = MAX_UFREQ;
if (freq_ > (MAX_UFREQ - def_step)) {
freq_ = MAX_UFREQ;
}
button_frequency.set_encoder_delta(0);
receiver_model.set_tuning_frequency(freq); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq) + " MHz>");
receiver_model.set_target_frequency(freq_); // Retune to actual freq
button_frequency.set_text("<" + to_string_short_freq(freq_) + " MHz>");
};
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
@@ -146,7 +118,7 @@ LevelView::LevelView(NavigationView& nav)
audio::output::stop();
} else if (v == 1) {
audio::output::start();
this->on_headphone_volume_changed((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
} else {
}
};
@@ -165,41 +137,29 @@ LevelView::LevelView(NavigationView& nav)
// FILL STEP OPTIONS
freqman_set_modulation_option(field_mode);
freqman_set_step_option_short(step_mode);
freq_stats_rssi.set_style(&style_white);
freq_stats_db.set_style(&style_white);
}
void LevelView::on_headphone_volume_changed(int32_t v) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
freq_stats_rssi.set_style(&Styles::white);
freq_stats_db.set_style(&Styles::white);
}
void LevelView::on_statistics_update(const ChannelStatistics& statistics) {
static int last_max_db = -1000;
static int last_min_rssi = -1000;
static int last_avg_rssi = -1000;
static int last_max_rssi = -1000;
static int16_t last_max_db = -1000;
static int16_t last_min_rssi = -1000;
static int16_t last_avg_rssi = -1000;
static int16_t last_max_rssi = -1000;
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
bool refresh_db = false;
bool refresh_rssi = false;
// refresh db
if (last_max_db != statistics.max_db) {
refresh_db = true;
}
if (last_min_rssi != rssi.get_min() || last_avg_rssi != rssi.get_avg() || last_max_rssi != rssi.get_max()) {
refresh_rssi = true;
}
if (refresh_db) {
last_max_db = statistics.max_db;
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
}
if (refresh_rssi) {
last_min_rssi = rssi.get_min();
last_avg_rssi = rssi.get_avg();
last_max_rssi = rssi.get_max();
freq_stats_rssi.set("RSSI: " + to_string_dec_int(rssi.get_min()) + "/" + to_string_dec_int(rssi.get_avg()) + "/" + to_string_dec_int(rssi.get_max()) + ",dt: " + to_string_dec_int(rssi.get_delta()));
// 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()) {
last_min_rssi = rssi_graph.get_graph_min();
last_avg_rssi = rssi_graph.get_graph_avg();
last_max_rssi = rssi_graph.get_graph_max();
freq_stats_rssi.set("RSSI: " + to_string_dec_int(last_min_rssi) + "/" + to_string_dec_int(last_avg_rssi) + "/" + to_string_dec_int(last_max_rssi) + ",dt: " + to_string_dec_int(rssi_graph.get_graph_delta()));
}
} /* on_statistic_updates */
@@ -209,74 +169,64 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
switch (new_mod) {
case AM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw DSB (0) default
field_bw.set_selected_index(0);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_am_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_am_configuration(n); };
// bw DSB (0) default
field_bw.set_by_value(0);
text_ctcss.set(" ");
break;
case NFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 16k (2) default
field_bw.set_selected_index(2);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_nbfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_nbfm_configuration(n); };
// bw 16k (2) default
field_bw.set_by_value(2);
break;
case WFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
// bw 200k (0) only/default
field_bw.set_selected_index(0);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_wfm_configuration(field_bw.selected_index_value());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
// bw 200k (0) only/default
field_bw.set_by_value(0);
text_ctcss.set(" ");
break;
case SPEC_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_capture);
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
// Baseband needs to know the desired sampling and oversampling rates.
baseband::set_sample_rate(sampling_rate, get_oversample_rate(sampling_rate));
// The radio needs to know the effective sampling rate.
auto actual_sampling_rate = get_actual_sample_rate(sampling_rate);
receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
};
field_bw.set_by_value(0);
default:
break;
}
if (new_mod != SPEC_MODULATION) {
// Reset receiver model to fix bug when going from SPEC to audio, the sound is distorted.
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
}
return step_mode.selected_index();
}
void LevelView::handle_coded_squelch(const uint32_t value) {
static int32_t last_idx = -1;
float diff, min_diff = value;
size_t min_idx{0};
size_t c;
if (field_mode.selected_index() != NFM_MODULATION) {
text_ctcss.set(" ");
return;
}
// Find nearest match
for (c = 0; c < tone_keys.size(); c++) {
diff = abs(((float)value / 100.0) - tone_keys[c].second);
if (diff < min_diff) {
min_idx = c;
min_diff = diff;
}
}
// Arbitrary confidence threshold
if (last_idx < 0 || (unsigned)last_idx != min_idx) {
last_idx = min_idx;
if (min_diff < 40)
text_ctcss.set("T: " + tone_keys[min_idx].first);
else
text_ctcss.set(" ");
}
if (field_mode.selected_index() == NFM_MODULATION)
text_ctcss.set(tone_key_string_by_value(value, text_ctcss.parent_rect().width() / 8));
else
text_ctcss.set(" ");
}
} /* namespace ui */
+25 -72
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -23,20 +24,21 @@
#ifndef _UI_LEVEL
#define _UI_LEVEL
#include "ui.hpp"
#include "receiver_model.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "freqman.hpp"
#include "analog_audio_app.hpp"
#include "audio.hpp"
#include "ui_mictx.hpp"
#include "portapack_persistent_memory.hpp"
#include "baseband_api.hpp"
#include "ui_spectrum.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "app_settings.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman_db.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "string_format.hpp"
#include "ui.hpp"
#include "ui_mictx.hpp"
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
#include "ui_styles.hpp"
namespace ui {
@@ -47,62 +49,22 @@ class LevelView : public View {
void focus() override;
const Style style_grey{
// level
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_white{
// level
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_yellow{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::yellow(),
};
const Style style_green{
// Found signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
const Style style_red{
// erasing freq
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
const Style style_blue{
// quick level, wait == 0
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::blue(),
};
std::string title() const override { return "Level"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_level", app_settings::Mode::RX};
size_t change_mode(freqman_index_t mod_type);
void on_statistics_update(const ChannelStatistics& statistics);
void set_display_freq(int64_t freq);
bool check_sd_card();
// TODO: needed?
int32_t db{0};
long long int MAX_UFREQ = {7200000000}; // maximum usable freq
bool sd_card_mounted = false;
rf::Frequency freq = {0};
rf::Frequency freq_ = {0};
Labels labels{
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Color::light_grey()},
@@ -118,13 +80,8 @@ class LevelView : public View {
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
NumberField field_volume{
{24 * 8, 0 * 16},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
OptionsField field_bw{
{3 * 8, 1 * 16},
@@ -133,7 +90,7 @@ class LevelView : public View {
OptionsField field_mode{
{15 * 8, 1 * 16},
3,
4,
{}};
OptionsField step_mode{
@@ -152,11 +109,11 @@ class LevelView : public View {
{"audio off", 0},
{"audio on", 1}
//{"tone on", 2},
//{"tone off", 2},
//{"tone off", 3},
}};
Text text_ctcss{
{22 * 8, 3 * 16 + 4, 14 * 8, 1 * 8},
{22 * 8, 3 * 16 + 4, 8 * 8, 1 * 8},
""};
// RSSI: XX/XX/XXX,dt: XX
@@ -203,7 +160,6 @@ class LevelView : public View {
};
void handle_coded_squelch(const uint32_t value);
void on_headphone_volume_changed(int32_t v);
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
@@ -217,9 +173,6 @@ class LevelView : public View {
[this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
};
} /* namespace ui */
+283 -361
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2020 euquiq
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* This file is part of PortaPack.
*
@@ -21,12 +22,15 @@
*/
#include "ui_looking_glass_app.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace ui {
void GlassView::focus() {
button_marker.focus();
range_presets.focus();
}
GlassView::~GlassView() {
@@ -37,7 +41,7 @@ GlassView::~GlassView() {
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power) {
if (mode == LOOKING_GLASS_SINGLEPASS) {
// analog audio app like view
// <20MHz spectrum mode
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - 120 + bin];
@@ -45,80 +49,51 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
if (spectrum.db[bin - 120] > max_power)
max_power = spectrum.db[bin - 120];
}
} else if (mode == LOOKING_GLASS_FASTSCAN) {
// view is made in multiple pass, use original bin picking
// Center 12 bins are ignored (DC spike is blanked) Leftmost and rightmost 2 bins are ignored
} else {
// FAST or SLOW mode
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - 2 - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - 2 - 120 + bin];
if (spectrum.db[134 + bin] > max_power)
max_power = spectrum.db[134 + bin];
} else {
if (spectrum.db[2 + bin - 120] > max_power)
max_power = spectrum.db[2 + bin - 120];
}
} else // if( mode == LOOKING_GLASS_SLOWSCAN )
{
if (bin < 120) {
if (spectrum.db[SPEC_NB_BINS - offset - 120 + bin] > max_power)
max_power = spectrum.db[SPEC_NB_BINS - offset - 120 + bin];
} else {
if (spectrum.db[offset + bin - 120] > max_power)
max_power = spectrum.db[offset + bin - 120];
if (spectrum.db[bin - 118] > max_power)
max_power = spectrum.db[bin - 118];
}
}
}
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
} else
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
return freq_at_pos;
}
void GlassView::on_marker_change() {
if (mode == LOOKING_GLASS_SINGLEPASS) {
marker = f_min + (marker_pixel_index * looking_glass_range) / SCREEN_W;
} else // if( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
marker = f_min + (offset * each_bin_size) + (marker_pixel_index * looking_glass_range) / SCREEN_W;
}
button_marker.set_text(to_string_short_freq(marker));
PlotMarker(marker_pixel_index); // Refresh marker on screen
}
// Returns the next multiple of num that is a multiple of multiplier
int64_t GlassView::next_mult_of(int64_t num, int64_t multiplier) {
return ((num / multiplier) + 1) * multiplier;
}
void GlassView::adjust_range(int64_t* f_min, int64_t* f_max, int64_t width) {
int64_t span = *f_max - *f_min;
int64_t num_intervals = span / width;
if (span % width != 0) {
num_intervals++;
}
int64_t new_span = num_intervals * width;
int64_t delta_span = (new_span - span) / 2;
*f_min -= delta_span;
*f_max += delta_span;
marker = get_freq_from_bin_pos(marker_pixel_index);
field_marker.set_text(to_string_short_freq(marker));
plot_marker(marker_pixel_index); // Refresh marker on screen
}
void GlassView::retune() {
// Start a new sweep
radio::set_tuning_frequency(f_center); // tune rx for this new slice directly, faster than using persistent memory saving
chThdSleepMilliseconds(5);
// Start a new sweep.
// Tune rx for this new slice directly because the model
// saves to persistent memory which is slower.
radio::set_tuning_frequency(f_center);
chThdSleepMilliseconds(5); // stabilize freq
baseband::spectrum_streaming_start(); // Do the RX
}
void GlassView::on_lna_changed(int32_t v_db) {
receiver_model.set_lna(v_db);
}
void GlassView::on_vga_changed(int32_t v_db) {
receiver_model.set_vga(v_db);
}
void GlassView::reset_live_view(bool clear_screen) {
void GlassView::reset_live_view() {
max_freq_hold = 0;
max_freq_power = -1000;
if (clear_screen) {
// only clear screen in peak mode
if (live_frequency_view == 2) {
display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, 320 - (108 + 16)}}, {0, 0, 0});
}
}
// Clear screen in peak mode.
if (live_frequency_view == 2)
display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0});
}
void GlassView::add_spectrum_pixel(uint8_t power) {
@@ -143,22 +118,21 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
// save max powerwull freq
if (spectrum_data[xpos] > max_freq_power) {
max_freq_power = spectrum_data[xpos];
max_freq_hold = f_center + ((looking_glass_range)*xpos) / SCREEN_W;
max_freq_hold = get_freq_from_bin_pos(xpos);
}
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
// clear if not in peak view
if (live_frequency_view != 2) {
display.fill_rectangle({{xpos, 108 + 16}, {1, 320 - point}}, {0, 0, 0});
display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0});
}
display.fill_rectangle({{xpos, 320 - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
}
if (last_max_freq != max_freq_hold) {
last_max_freq = max_freq_hold;
freq_stats.set("MAX HOLD: " + to_string_short_freq(max_freq_hold));
}
PlotMarker(marker_pixel_index);
plot_marker(marker_pixel_index);
} else {
display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier
}
@@ -166,43 +140,58 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
}
}
bool GlassView::process_bins(uint8_t* powerlevel) {
bins_hz_size += each_bin_size; // add pixel to fulfilled bag of Hz
if (bins_hz_size >= marker_pixel_step) // new pixel fullfilled
{
if (*powerlevel > min_color_power)
add_spectrum_pixel(*powerlevel); // Pixel will represent max_power
else
add_spectrum_pixel(0); // Filtered out, show black
*powerlevel = 0;
if (!pixel_index) // Received indication that a waterfall line has been completed
{
bins_hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center = f_center_ini;
retune();
} else
baseband::spectrum_streaming_start();
return true; // signal a new line
}
bins_hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
}
return false;
}
// Apparently, the spectrum object returns an array of SPEC_NB_BINS (256) bins
// Each having the radio signal power for it's corresponding frequency slot
// Each having the radio signal power for its corresponding frequency slot
void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
// we actually need SCREEN_W (240) of those bins
for (bin = offset; bin < bin_length + offset; bin++) {
for (uint8_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (ignore_dc && bin == 119) {
// process dc spike if enable
if (bin == 119) {
uint8_t next_max_power = 0;
get_max_power(spectrum, 120, next_max_power);
bins_Hz_size += 12 * each_bin_size; // add the ignored DC spike to "pixel fulfilled bag of Hz"
max_power = (max_power + next_max_power) / 2;
}
bins_Hz_size += each_bin_size; // add this bin Hz count into the "pixel fulfilled bag of Hz"
if (bins_Hz_size >= marker_pixel_step) // new pixel fullfilled
{
if (min_color_power < max_power)
add_spectrum_pixel(max_power); // Pixel will represent max_power
else
add_spectrum_pixel(0); // Filtered out, show black
max_power = 0;
if (!pixel_index) // Received indication that a waterfall line has been completed
{
bins_Hz_size = 0; // Since this is an entire pixel line, we don't carry "Pixels into next bin"
f_center = f_center_ini;
retune();
return; // signal a new line
get_max_power(spectrum, bin + 1, next_max_power);
for (uint8_t it = 0; it < ignore_dc; it++) {
uint8_t med_max_power = (max_power + next_max_power) / 2; // due to the way process_bins works we have to keep resetting the color
if (process_bins(&med_max_power) == true)
return; // new line signaled, return
}
bins_Hz_size -= marker_pixel_step; // reset bins size, but carrying the eventual excess Hz into next pixel
}
// process actual bin
if (process_bins(&max_power) == true)
return; // new line signaled, return
}
f_center += looking_glass_step;
retune();
if (mode != LOOKING_GLASS_SINGLEPASS) {
f_center += looking_glass_step;
retune();
} else
baseband::spectrum_streaming_start();
}
void GlassView::on_hide() {
@@ -216,80 +205,62 @@ void GlassView::on_show() {
}
void GlassView::on_range_changed() {
reset_live_view(false);
reset_live_view();
f_min = field_frequency_min.value();
f_max = field_frequency_max.value();
f_min = f_min * MHZ_DIV; // Transpose into full frequency realm
f_max = f_max * MHZ_DIV;
looking_glass_range = f_max - f_min;
if (looking_glass_range < LOOKING_GLASS_SLICE_WIDTH_MAX) {
mode = LOOKING_GLASS_SINGLEPASS;
} else {
mode = scan_type.selected_index_value();
}
if (mode == LOOKING_GLASS_SINGLEPASS) {
if (looking_glass_range <= LOOKING_GLASS_SLICE_WIDTH_MAX) {
// if the view is done in one pass, show it like in analog_audio_app
offset = 0;
mode = LOOKING_GLASS_SINGLEPASS;
offset = 2;
bin_length = SCREEN_W;
ignore_dc = 0;
} else if (mode == LOOKING_GLASS_FASTSCAN) {
// view is made in multiple pass, use original bin picking
offset = 0;
bin_length = SCREEN_W;
ignore_dc = 1;
} else // if( mode == LOOKING_GLASS_SLOWSCAN )
{
offset = 16;
bin_length = 80;
ignore_dc = 0;
}
each_bin_size = looking_glass_bandwidth / SPEC_NB_BINS;
if (mode != LOOKING_GLASS_SINGLEPASS) {
looking_glass_step = (bin_length + (ignore_dc * 12)) * each_bin_size;
} else {
looking_glass_step = SPEC_NB_BINS * each_bin_size;
}
adjust_range(&f_min, &f_max, SCREEN_W);
looking_glass_range = f_max - f_min;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
search_span = looking_glass_range / MHZ_DIV;
button_range.set_text(" "); // clear up to 6 chars
if (locked_range) {
button_range.set_text(">" + to_string_dec_uint(search_span) + "<");
} else {
button_range.set_text(" " + to_string_dec_uint(search_span) + " ");
}
pixel_index = 0; // reset pixel counter
max_power = 0; // reset save max power level
bins_Hz_size = 0; // reset amount of Hz filled up by pixels
if (mode == LOOKING_GLASS_SINGLEPASS) {
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_bandwidth / 2;
looking_glass_sampling_rate = looking_glass_bandwidth;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
} else // if ( mode == LOOKING_GLASS_SLOWSCAN || mode == LOOKING_GLASS_FASTSCAN )
{
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else {
// view is made in multiple pass, use original bin picking
mode = scan_type.selected_index_value();
looking_glass_bandwidth = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = looking_glass_bandwidth / SPEC_NB_BINS;
looking_glass_sampling_rate = LOOKING_GLASS_SLICE_WIDTH_MAX;
each_bin_size = LOOKING_GLASS_SLICE_WIDTH_MAX / SPEC_NB_BINS;
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
ignore_dc = 4;
bin_length = SCREEN_W;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 2;
bin_length = 80;
ignore_dc = 0;
}
looking_glass_step = (bin_length + ignore_dc) * each_bin_size;
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
search_span = looking_glass_range / MHZ_DIV;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
pixel_index = 0;
max_power = 0;
bins_hz_size = 0;
on_marker_change();
update_range_field();
// set the sample rate and bandwidth
receiver_model.set_sampling_rate(looking_glass_sampling_rate);
receiver_model.set_baseband_bandwidth(looking_glass_bandwidth);
receiver_model.set_squelch_level(0);
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
f_center = f_center_ini; // Reset sweep into first slice
baseband::set_spectrum(looking_glass_sampling_rate, field_trigger.value());
receiver_model.set_tuning_frequency(f_center); // tune rx for this slice
f_center = f_center_ini; // Reset sweep into first slice
baseband::set_spectrum(looking_glass_bandwidth, trigger);
receiver_model.set_target_frequency(f_center); // tune rx for this slice
}
void GlassView::PlotMarker(uint8_t pos) {
void GlassView::plot_marker(uint8_t pos) {
uint8_t shift_y = 0;
if (live_frequency_view > 0) // plot one line down when in live view
{
@@ -301,6 +272,50 @@ void GlassView::PlotMarker(uint8_t pos) {
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Color::red()); // Red marker bottom
}
void GlassView::update_min(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v > 7200 - min_size) {
v = 7200 - min_size;
}
if (v > (field_frequency_max.value() - min_size))
field_frequency_max.set_value(v + min_size, false);
if (locked_range)
field_frequency_max.set_value(v + min_size, false);
else
field_frequency_min.set_value(v, false);
}
void GlassView::update_max(int32_t v) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v < min_size) {
v = min_size;
}
if (v < (field_frequency_min.value() + min_size))
field_frequency_min.set_value(v - min_size, false);
if (locked_range)
field_frequency_min.set_value(v - min_size, false);
else
field_frequency_max.set_value(v, false);
}
void GlassView::update_range_field() {
if (!locked_range) {
field_range.set_style(&Styles::white);
field_range.set_text(" " + to_string_dec_uint(search_span) + " ");
} else {
field_range.set_style(&Styles::red);
field_range.set_text(">" + to_string_dec_uint(search_span) + "<");
}
}
GlassView::GlassView(
NavigationView& nav)
: nav_(nav) {
@@ -311,7 +326,7 @@ GlassView::GlassView(
&field_frequency_max,
&field_lna,
&field_vga,
&button_range,
&field_range,
&steps_config,
&scan_type,
&view_config,
@@ -319,305 +334,212 @@ GlassView::GlassView(
&filter_config,
&field_rf_amp,
&range_presets,
&button_marker,
&field_marker,
&field_trigger,
&button_jump,
&button_rst,
&freq_stats});
load_Presets(); // Load available presets from TXT files (or default)
load_presets(); // Load available presets from TXT files (or default).
preset_index = clip<uint8_t>(preset_index, 0, presets_db.size());
field_frequency_min.set_value(f_min / MHZ_DIV);
field_frequency_min.on_change = [this](int32_t v) {
reset_live_view(true);
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v > 7200 - min_size) {
v = 7200 - min_size;
field_frequency_min.set_value(v);
}
if (v > (field_frequency_max.value() - min_size))
field_frequency_max.set_value(v + min_size);
if (locked_range)
field_frequency_max.set_value(v + min_size);
range_presets.set_selected_index(0); // Manual
update_min(v);
on_range_changed();
};
field_frequency_min.set_value(presets_db[0].min); // Defaults to first preset
field_frequency_min.set_step(steps);
field_frequency_min.on_select = [this, &nav](NumberField& field) {
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_min.value() * MHZ_DIV);
new_view->on_changed = [this, &field](rf::Frequency f) {
int32_t freq = f / MHZ_DIV;
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (freq > (7200 - min_size))
freq = 7200 - min_size;
field_frequency_min.set_value(freq);
if (field_frequency_max.value() < (freq + min_size))
field_frequency_max.set_value(freq + min_size);
on_range_changed();
field_frequency_min.set_value(f / MHZ_DIV);
};
};
field_frequency_max.set_value(f_max / MHZ_DIV);
field_frequency_max.on_change = [this](int32_t v) {
reset_live_view(true);
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
if (v < min_size) {
v = min_size;
field_frequency_max.set_value(v);
}
if (v < (field_frequency_min.value() + min_size))
field_frequency_min.set_value(v - min_size);
if (locked_range)
field_frequency_min.set_value(v - min_size);
range_presets.set_selected_index(0); // Manual
update_max(v);
on_range_changed();
};
field_frequency_max.set_value(presets_db[0].max); // Defaults to first preset
field_frequency_max.set_step(steps);
field_frequency_max.on_select = [this, &nav](NumberField& field) {
auto new_view = nav_.push<FrequencyKeypadView>(field_frequency_max.value() * MHZ_DIV);
new_view->on_changed = [this, &field](rf::Frequency f) {
int32_t min_size = steps;
if (locked_range)
min_size = search_span;
if (min_size < 2)
min_size = 2;
int32_t freq = f / MHZ_DIV;
if (freq < min_size)
freq = min_size;
field_frequency_max.set_value(freq);
if (field_frequency_min.value() > (freq - min_size))
field_frequency_min.set_value(freq - min_size);
on_range_changed();
field_frequency_max.set_value(f / MHZ_DIV);
};
};
field_lna.on_change = [this](int32_t v) {
reset_live_view(true);
this->on_lna_changed(v);
};
field_lna.set_value(receiver_model.lna());
field_vga.on_change = [this](int32_t v_db) {
reset_live_view(true);
this->on_vga_changed(v_db);
};
field_vga.set_value(receiver_model.vga());
steps_config.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
steps_config.on_change = [this](size_t, OptionsField::value_t v) {
field_frequency_min.set_step(v);
field_frequency_max.set_step(v);
steps = v;
};
steps_config.set_selected_index(0); // default of 1 Mhz steps
steps_config.set_selected_index(0); // 1 Mhz step.
scan_type.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
scan_type.on_change = [this](size_t, OptionsField::value_t v) {
mode = v;
on_range_changed();
};
scan_type.set_selected_index(0); // default legacy fast scan
scan_type.set_selected_index(mode);
view_config.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
// clear between changes
reset_live_view(true);
if (v == 0) {
live_frequency_view = 0;
level_integration.hidden(true);
freq_stats.hidden(true);
button_jump.hidden(true);
button_rst.hidden(true);
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
} else if (v == 1) {
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
live_frequency_view = 1;
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
button_jump.hidden(false);
button_rst.hidden(false);
} else if (v == 2) {
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
live_frequency_view = 2;
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
button_jump.hidden(false);
button_rst.hidden(false);
view_config.on_change = [this](size_t, OptionsField::value_t v) {
reset_live_view(); // Clear between changes.
live_frequency_view = v;
switch (v) {
case 0: // SPEC
level_integration.hidden(true);
freq_stats.hidden(true);
button_jump.hidden(true);
button_rst.hidden(true);
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates.
break;
case 1: // LEVEL
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
button_jump.hidden(false);
button_rst.hidden(false);
break;
case 2: // PEAK
default:
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
button_jump.hidden(false);
button_rst.hidden(false);
break;
}
set_dirty();
};
view_config.set_selected_index(0); // default spectrum
view_config.set_selected_index(live_frequency_view);
level_integration.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
reset_live_view(true);
level_integration.on_change = [this](size_t, OptionsField::value_t v) {
reset_live_view();
live_frequency_integrate = v;
};
level_integration.set_selected_index(3); // default integration of ( 3 * old value + new_value ) / 4
level_integration.set_selected_index(live_frequency_integrate);
filter_config.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
reset_live_view(true);
filter_config.on_change = [this](size_t ix, OptionsField::value_t v) {
reset_live_view();
min_color_power = v;
filter_index = ix;
};
filter_config.set_selected_index(0);
filter_config.set_selected_index(filter_index);
range_presets.on_change = [this](size_t n, OptionsField::value_t v) {
(void)n;
range_presets.on_change = [this](size_t ix, OptionsField::value_t v) {
preset_index = ix;
if (ix == 0) return; // Don't update range for "Manual".
// NB: Don't trigger updates, presets directly set the range
// values without applying step or range lock.
field_frequency_min.set_value(presets_db[v].min, false);
field_frequency_max.set_value(presets_db[v].max, false);
on_range_changed();
};
range_presets.set_selected_index(preset_index);
button_marker.on_change = [this]() {
if (((int)marker_pixel_index + button_marker.get_encoder_delta()) < 0) {
marker_pixel_index = 0;
} else if (((int)marker_pixel_index + button_marker.get_encoder_delta()) > SCREEN_W) {
marker_pixel_index = SCREEN_W;
} else {
marker_pixel_index = marker_pixel_index + button_marker.get_encoder_delta();
}
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
marker_pixel_index = clip<uint8_t>(marker_pixel_index + delta, 0, SCREEN_W);
on_marker_change();
button_marker.set_encoder_delta(0);
};
button_marker.on_select = [this](ButtonWithEncoder&) {
receiver_model.set_tuning_frequency(marker); // Center tune rx in marker freq.
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
nav_.pop();
nav_.push<AnalogAudioView>(); // Jump into audio view
field_marker.on_select = [this](TextField&) {
// Launch Audio with marker frequency.
launch_audio(marker);
};
field_trigger.on_change = [this](int32_t v) {
baseband::set_spectrum(looking_glass_sampling_rate, v);
trigger = v;
baseband::set_spectrum(looking_glass_bandwidth, trigger);
};
field_trigger.set_value(32); // Defaults to 32, as normal triggering resolution
field_trigger.set_value(trigger);
button_range.on_select = [this](Button&) {
if (locked_range) {
locked_range = false;
button_range.set_style(&style_white);
button_range.set_text(" " + to_string_dec_uint(search_span) + " ");
} else {
locked_range = true;
button_range.set_style(&style_red);
button_range.set_text(">" + to_string_dec_uint(search_span) + "<");
}
field_range.on_select = [this](TextField&) {
locked_range = !locked_range;
update_range_field();
};
button_jump.on_select = [this](Button&) {
receiver_model.set_tuning_frequency(max_freq_hold); // Center tune rx in marker freq.
receiver_model.set_frequency_step(MHZ_DIV); // Preset a 1 MHz frequency step into RX -> AUDIO
nav_.pop();
nav_.push<AnalogAudioView>(); // Jump into audio view
// Launch Audio with peak frequency.
launch_audio(max_freq_hold);
};
button_rst.on_select = [this](Button&) {
reset_live_view(true);
reset_live_view();
};
display.scroll_set_area(109, 319);
baseband::set_spectrum(looking_glass_sampling_rate, field_trigger.value()); // trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute ,
// it keeps adding the output of the fft to the buffer until "trigger" number of calls are made,
// at which time it pushes the buffer up with channel_spectrum.feed
marker_pixel_index = 120;
on_range_changed();
// trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute,
// it keeps adding the output of the fft to the buffer until "trigger" number of calls are made,
// at which time it pushes the buffer up with channel_spectrum.feed
baseband::set_spectrum(looking_glass_bandwidth, trigger);
marker_pixel_index = SCREEN_W / 2;
on_range_changed(); // Force a UI update.
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
receiver_model.set_baseband_bandwidth(looking_glass_bandwidth); // possible values: 1.75/2.5/3.5/5/5.5/6/7/8/9/10/12/14/15/20/24/28MHz
receiver_model.set_squelch_level(0);
receiver_model.enable();
}
void GlassView::load_Presets() {
File presets_file; // LOAD /WHIPCALC/ANTENNAS.TXT from microSD
auto result = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
presets_db.clear(); // Start with fresh db
if (result.is_valid()) {
presets_Default(); // There is no txt, store a default range
} else {
std::string line; // There is a txt file
char one_char[1]; // Read it char by char
for (size_t pointer = 0; pointer < presets_file.size(); pointer++) {
presets_file.seek(pointer);
presets_file.read(one_char, 1);
if ((int)one_char[0] > 31) { // ascii space upwards
line += one_char[0]; // Add it to the textline
} else if (one_char[0] == '\n') { // New Line
txtline_process(line); // make sense of this textline
line.clear(); // Ready for next textline
}
void GlassView::load_presets() {
File presets_file;
auto error = presets_file.open("LOOKINGGLASS/PRESETS.TXT");
presets_db.clear();
// Add the "Manual" entry.
presets_db.push_back({0, 0, "Manual"});
if (!error) {
auto reader = FileLineReader(presets_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue;
auto cols = split_string(line, ',');
if (cols.size() != 3)
continue;
preset_entry entry{};
parse_int(cols[0], entry.min);
parse_int(cols[1], entry.max);
entry.label = trimr(cols[2]);
if (entry.min == 0 || entry.max == 0 || entry.min >= entry.max)
continue; // Invalid line.
presets_db.emplace_back(std::move(entry));
}
if (line.length() > 0)
txtline_process(line); // Last line had no newline at end ?
if (!presets_db.size())
presets_Default(); // no antenna on txt, use default
}
populate_Presets();
populate_presets();
}
void GlassView::txtline_process(std::string& line) {
if (line.find("#") != std::string::npos)
return; // Line is just a comment
size_t comma = line.find(","); // Get first comma position
if (comma == std::string::npos)
return; // No comma at all
size_t previous = 0;
preset_entry new_preset;
new_preset.min = std::stoi(line.substr(0, comma));
if (!new_preset.min)
return; // No frequency!
previous = comma + 1;
comma = line.find(",", previous); // Search for next delimiter
if (comma == std::string::npos)
return; // No comma at all
new_preset.max = std::stoi(line.substr(previous, comma - previous));
if (!new_preset.max)
return; // No frequency!
new_preset.label = line.substr(comma + 1);
if (new_preset.label.size() == 0)
return; // No label ?
presets_db.push_back(new_preset); // Add this preset.
}
void GlassView::populate_Presets() {
void GlassView::populate_presets() {
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t entries;
for (preset_entry preset : presets_db) { // go thru all available presets
for (const auto& preset : presets_db)
entries.emplace_back(preset.label, entries.size());
}
range_presets.set_options(entries);
range_presets.set_options(std::move(entries));
}
void GlassView::presets_Default() {
presets_db.clear();
presets_db.push_back({2320, 2560, "DEFAULT WIFI 2.4GHz"});
void GlassView::launch_audio(rf::Frequency center_freq) {
receiver_model.set_target_frequency(center_freq);
auto settings = receiver_model.settings();
settings.frequency_step = MHZ_DIV; // Preset a 1 MHz frequency step into RX -> AUDIO
nav_.replace<AnalogAudioView>(settings); // Jump into audio view
}
} // namespace ui
@@ -26,16 +26,20 @@
#include "ui.hpp"
#include "portapack.hpp"
#include "app_settings.hpp"
#include "baseband_api.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_styles.hpp"
#include "string_format.hpp"
#include "analog_audio_app.hpp"
#include "spectrum_color_lut.hpp"
namespace ui {
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
#define MHZ_DIV 1000000
@@ -59,7 +63,7 @@ class GlassView : public View {
GlassView& operator=(const GlassView& nav);
~GlassView();
std::string title() const override { return "LookingGlass"; };
std::string title() const override { return "Looking Glass"; };
void on_show() override;
void on_hide() override;
@@ -67,6 +71,29 @@ class GlassView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{ReceiverModel::Mode::SpectrumAnalysis};
// Settings
rf::Frequency f_min = 260 * MHZ_DIV; // Default to 315/433 remote range.
rf::Frequency f_max = 500 * MHZ_DIV;
uint8_t preset_index = 0; // Manual
uint8_t filter_index = 0; // OFF
uint8_t trigger = 32;
uint8_t mode = LOOKING_GLASS_FASTSCAN;
uint8_t live_frequency_view = 0; // Spectrum
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
app_settings::SettingsManager settings_{
"rx_glass"sv,
app_settings::Mode::RX,
{
{"min"sv, &f_min},
{"max"sv, &f_max},
{"preset"sv, &preset_index},
{"filter"sv, &filter_index},
{"trigger"sv, &trigger},
{"scan_mode"sv, &mode},
{"freq_view"sv, &live_frequency_view},
{"freq_integrate"sv, &live_frequency_integrate},
}};
struct preset_entry {
rf::Frequency min{};
@@ -74,81 +101,66 @@ class GlassView : public View {
std::string label{};
};
const Style style_white{
// free range
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::white(),
};
const Style style_red{
// locked range
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red(),
};
std::vector<preset_entry> presets_db{};
void update_min(int32_t v);
void update_max(int32_t v);
void update_range_field();
void get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint8_t pos);
void on_marker_change();
int64_t next_mult_of(int64_t num, int64_t multiplier);
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
void retune();
bool move_to_next_position();
bool process_bins(uint8_t* powerlevel);
void on_channel_spectrum(const ChannelSpectrum& spectrum);
void do_timers();
int64_t next_mult_of(int64_t num, int64_t multiplier);
void adjust_range(int64_t* f_min, int64_t* f_max, int64_t width);
void on_range_changed();
void on_lna_changed(int32_t v_db);
void on_vga_changed(int32_t v_db);
void reset_live_view(bool clear_screen);
void reset_live_view();
void add_spectrum_pixel(uint8_t power);
void PlotMarker(uint8_t pos);
void load_Presets();
void txtline_process(std::string& line);
void populate_Presets();
void presets_Default();
void plot_marker(uint8_t pos);
void load_presets();
void populate_presets();
void launch_audio(rf::Frequency center_freq);
rf::Frequency f_min{0}, f_max{0};
rf::Frequency search_span{0};
rf::Frequency f_center{0};
rf::Frequency f_center_ini{0};
rf::Frequency marker{0};
uint8_t marker_pixel_index{0};
rf::Frequency marker_pixel_step{0};
// size of one spectrum bin in Hz
// Size of one spectrum bin in Hz.
rf::Frequency each_bin_size{0};
// consumed number of Hz, used to know if we have filled a 'bag' , a corresponding pixel length on screen
rf::Frequency bins_Hz_size{0};
// Bandwidth of a single spectrum bin.
rf::Frequency bins_hz_size{0};
rf::Frequency looking_glass_sampling_rate{0};
rf::Frequency looking_glass_bandwidth{0};
rf::Frequency looking_glass_range{0};
rf::Frequency looking_glass_step{0};
uint8_t min_color_power{0};
uint8_t min_color_power{0}; // Filter cutoff level.
uint32_t pixel_index{0};
std::array<Color, SCREEN_W> spectrum_row = {0};
std::array<uint8_t, SCREEN_W> spectrum_data = {0};
ChannelSpectrumFIFO* fifo{nullptr};
uint8_t max_power = 0;
int32_t steps = 0;
uint8_t live_frequency_view = 0;
int16_t live_frequency_integrate = 3;
int64_t max_freq_hold = 0;
int16_t max_freq_power = -1000;
std::array<Color, SCREEN_W> spectrum_row{};
std::array<uint8_t, SCREEN_W> spectrum_data{};
ChannelSpectrumFIFO* fifo{};
int32_t steps = 1;
bool locked_range = false;
uint8_t max_power = 0;
rf::Frequency max_freq_hold = 0;
rf::Frequency last_max_freq = 0;
int16_t max_freq_power = -1000;
uint8_t bin_length = SCREEN_W;
uint8_t real_bin_length = SCREEN_W;
uint8_t offset = 0;
uint8_t tune_offset = 0;
uint8_t bin = 0;
int64_t last_max_freq = 0;
uint8_t mode = LOOKING_GLASS_FASTSCAN;
uint8_t ignore_dc = 0;
Labels labels{
{{0, 0}, "MIN: MAX: LNA VGA ", Color::light_grey()},
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Color::light_grey()},
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Color::light_grey()},
{{0, 2 * 16}, "PRESET:", Color::light_grey()},
{{0, 3 * 16}, "MARKER: MHz", Color::light_grey()},
{{0, 3 * 16}, "MARKER: MHz", Color::light_grey()},
{{0, 4 * 16}, "RES: STEP:", Color::light_grey()}};
NumberField field_frequency_min{
@@ -171,8 +183,8 @@ class GlassView : public View {
VGAGainField field_vga{
{27 * 8, 0 * 16}};
Button button_range{
{7 * 8, 1 * 16, 4 * 8, 16},
TextField field_range{
{6 * 8, 1 * 16, 6 * 8, 16},
""};
OptionsField filter_config{
@@ -180,23 +192,21 @@ class GlassView : public View {
4,
{
{"OFF ", 0},
{"MID ", 118}, // 85 +25 (110) + a bit more to kill all blue
{"MID ", 118}, // 85 + 25 (110) + a bit more to kill all blue
{"HIGH", 202}, // 168 + 25 (193)
}};
RFAmpField field_rf_amp{
{29 * 8, 1 * 16}};
{28 * 8, 1 * 16}};
OptionsField range_presets{
{7 * 8, 2 * 16},
20,
{
{" NONE (WIFI 2.4GHz)", 0},
}};
{}};
ButtonWithEncoder button_marker{
{7 * 8, 3 * 16, 10 * 8, 16},
" "};
TextField field_marker{
{7 * 8, 3 * 16, 9 * 8, 16},
""};
NumberField field_trigger{
{4 * 8, 4 * 16},
+398 -411
View File
@@ -22,22 +22,21 @@
#include "ui_mictx.hpp"
#include "baseband_api.hpp"
#include "audio.hpp"
#include "wm8731.hpp"
using wolfson::wm8731::WM8731;
#include "tonesets.hpp"
#include "portapack_hal.hpp"
#include "cpld_update.hpp"
#include "string_format.hpp"
#include "baseband_api.hpp"
#include "irq_controls.hpp"
#include "ui_freqman.hpp"
#include "portapack_hal.hpp"
#include "string_format.hpp"
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "wm8731.hpp"
#include <cstring>
using namespace tonekey;
using namespace portapack;
using wolfson::wm8731::WM8731;
WM8731 audio_codec_wm8731{i2c0, 0x1a};
@@ -52,7 +51,7 @@ void MicTXView::focus() {
field_rxfrequency.focus();
break;
default:
field_va.focus();
check_va.focus();
break;
}
}
@@ -61,23 +60,56 @@ void MicTXView::update_vumeter() {
vumeter.set_value(audio_level);
}
void MicTXView::update_tx_icon() {
tx_icon.set_foreground(transmitting ? Color::red() : Color::black());
tx_icon.set_background(transmitting ? Color::yellow() : Color::black());
}
void MicTXView::on_tx_progress(const bool done) {
// Roger beep played, stop transmitting
if (done)
set_tx(false);
}
uint8_t MicTXView::shift_bits(void) {
uint8_t shift_bits_s16{4}; // shift bits factor to the captured ADC S16 audio sample.
if (audio::debug::codec_name() == "WM8731") {
switch (ak4951_alc_and_wm8731_boost_GUI) {
case 0: // +12 dBs respect reference level orig fw 1.5.x fw FM : when +20dB's boost ON) and shift bits (>>8),
shift_bits_s16 = 6; // now mic-boost on (+20dBs) and shift bits (>>6), +20+12=32 dBs (orig fw +20 dBs+ 0dBs)=> +12dB's respect ref.
break;
case 1: // +06 dBs reference level, (when +20dB's boost ON)
shift_bits_s16 = 7; // now mic-boost on (+20dBs) and shift bits (>>7), +20+06=26 dBs (orig fw +20 dBs+ 0dBs) => +06dB's respect ref.
break;
case 2:
shift_bits_s16 = 4; // +04 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (4) (+0+24dB's)=24 dBs => +04dB's respect ref.
case 3:
shift_bits_s16 = 5; // -02 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (5) (+0+18dB's)=18 dBs => -02dB's respect ref.
case 4:
shift_bits_s16 = 6; // -08 dBs respect ref level, (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
}
} else {
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod, shift audio data for AK4951, using top 8 bits s16 data (>>8)
}
return shift_bits_s16;
}
void MicTXView::configure_baseband() {
// TODO: Can this use the transmitter model instead?
baseband::set_audiotx_config(
sampling_rate / 20, // Update vu-meter at 20Hz
transmitting ? transmitter_model.channel_bandwidth() : 0,
mic_gain,
shift_bits_s16, // to be used in dsp_modulate
mic_gain_x10 / 10.0,
shift_bits(), // to be used in dsp_modulate
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
enable_am,
enable_dsb,
enable_usb,
enable_lsb);
(mic_mod_index == MIC_MOD_AM),
(mic_mod_index == MIC_MOD_DSB),
(mic_mod_index == MIC_MOD_USB),
(mic_mod_index == MIC_MOD_LSB));
}
void MicTXView::set_tx(bool enable) {
@@ -86,7 +118,11 @@ void MicTXView::set_tx(bool enable) {
rxaudio(false); // Then turn off audio RX
transmitting = true;
configure_baseband();
transmitter_model.set_tuning_frequency(tx_frequency); // Now,no need: transmitter_model.set_tx_gain(tx_gain), nor (rf_amp);
transmitter_model.set_target_frequency(tx_frequency); // Now, no need: transmitter_model.set_tx_gain(tx_gain), nor (rf_amp);
/* The max. Power Spectrum Densitiy in WFM with High tone mod level (80%) and high 32kHZ subtone as fmod. with max fdeviation 150k ,
BW aprox = 2 *(150K + 32K) = 364khz, then we just select the minimum TX LPF 1M75. */
transmitter_model.set_baseband_bandwidth(1'750'000);
transmitter_model.enable();
portapack::pin_i2s0_rx_sda.mode(3); // This is already done in audio::init but gets changed by the CPLD overlay reprogramming
} else {
@@ -97,12 +133,24 @@ void MicTXView::set_tx(bool enable) {
transmitting = false;
configure_baseband();
transmitter_model.disable();
if (rx_enabled) // If audio RX is enabled and we've been transmitting
if (rx_enabled) {
rxaudio(true); // Turn back on audio RX
// TODO FIXME: this isn't working: vu meter isn't going to 0:
vumeter.set_value(0); // Reset vumeter
vumeter.dirty(); // Force to refresh vumeter.
}
}
}
update_tx_icon();
}
bool MicTXView::tx_button_held() {
const auto switches_state = get_switches_state();
return switches_state[(size_t)ui::KeyEvent::Select];
};
void MicTXView::do_timing() {
if (va_enabled) {
if (!transmitting) {
@@ -124,7 +172,8 @@ void MicTXView::do_timing() {
if ((decay_timer >> 8) >= decay_ms) {
decay_timer = 0;
attack_timer = 0;
set_tx(false);
if (!button_touch && !tx_button_held())
set_tx(false);
} else {
decay_timer += lcd_frame_duration;
}
@@ -134,54 +183,40 @@ void MicTXView::do_timing() {
}
} else {
// Check for PTT release
const auto switches_state = get_switches_state();
if (!switches_state[4] && transmitting && !button_touch) // Select button
if (transmitting && !button_touch && !tx_button_held())
set_tx(false);
}
}
/* Hmmmm. Maybe useless now.
void MicTXView::on_tuning_frequency_changed(rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
//if ( rx_enabled )
receiver_model.set_tuning_frequency(f); //Update freq also for RX
}
*/
void MicTXView::rxaudio(bool is_on) {
if (is_on) {
void MicTXView::rxaudio(bool enable) {
if (enable) {
audio::input::stop();
baseband::shutdown();
if (enable_am || enable_usb || enable_lsb || enable_dsb) { // "NFM/FM",0 ," WFM ",1 , " AM ",2, " USB ", 3, " LSB ",4, " DSB-SC", 5
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); // that AM demodulation engine is common to all Amplitude mod : AM/USB/LSB/DSB (2,3,4,5)
if (options_mode.selected_index() < 5) // We will be called here with 2,3,4,5 . We treat here demod. filter 2,3,4; (excluding DSB-C case (5) it is treated more down).
receiver_model.set_am_configuration(options_mode.selected_index() - 1); // selecting proper filter(2,3,4). 2-1=1=>6k-AM(1) , 3-1=2=>+3k-USB(2), 4-1=3=>-3K-LSB(3),
} else { // We are in NFM/FM or WFM (NFM BW:8k5 or 11k / FM BW 16k / WFM BW:200k)
if (enable_wfm) { // WFM , BW 200Khz aprox , or the two new addional BW filters (180k, 40k)
switch (mic_mod_index) {
case MIC_MOD_NFM: // NFM BW:8k5 or 11k / FM BW 16k
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
break;
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// receiver_model.set_wfm_configuration(n); // it is called above , depending user's selection (200k, 180k,40k).
} else { // NFM BW:8k5 or 11k / FM BW 16k
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); //
// receiver_model.set_nbfm_configuration(n); is called above , depending user's selection (8k5, 11k, 16k).
}
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
break;
case MIC_MOD_AM:
case MIC_MOD_DSB:
case MIC_MOD_USB:
case MIC_MOD_LSB:
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); // that AM demodulation engine is common to all Amplitude mod : AM/USB/LSB/DSB (2,3,4,5)
if (options_mode.selected_index() < 5) // We will be called here with 2,3,4,5. We treat here demod. filter 2,3,4; (excluding DSB-C case (5) it is treated more down).
receiver_model.set_am_configuration(options_mode.selected_index() - 1); // selecting proper filter(2,3,4). 2-1=1=>6k-AM(1), 3-1=2=>+3k-USB(2), 4-1=3=>-3K-LSB(3),
break;
}
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
// receiver_model.set_tuning_frequency(field_frequency.value()); //probably this too can be commented out.
if (bool_same_F_tx_rx_enabled) // when stop TX ,define to which freq RX we return
receiver_model.set_tuning_frequency(tx_frequency); // Update freq also for RX = TX
else
receiver_model.set_tuning_frequency(rx_frequency); // Now with seperate freq controls!
receiver_model.set_lna(rx_lna);
receiver_model.set_vga(rx_vga);
receiver_model.set_rf_amp(rx_amp);
receiver_model.set_target_frequency(bool_same_F_tx_rx_enabled ? tx_frequency : rx_frequency);
receiver_model.enable();
hackrf::cpld::load_sram_no_verify(); // to have a good RX without any ghost inside Mic App
audio::output::start();
} else { // These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); // This fixes something with AM RX...
@@ -190,21 +225,82 @@ void MicTXView::rxaudio(bool is_on) {
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
audio::output::stop();
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
portapack::pin_i2s0_rx_sda.mode(3);
configure_baseband();
}
}
void MicTXView::on_headphone_volume_changed(int32_t v) {
// if (rx_enabled) {
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
receiver_model.set_headphone_volume(new_volume);
//}
void MicTXView::set_rxbw_options(void) {
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t rxbw; // Aux structure to change dynamically field_rxbw contents
switch (mic_mod_index) {
case MIC_MOD_NFM:
freqman_set_bandwidth_option(NFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with NFM options from freqman_db.cpp
field_rxbw.set_by_value(receiver_model.nbfm_configuration());
break;
case MIC_MOD_WFM:
freqman_set_bandwidth_option(WFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with WFM options from freqman_db.cpp
field_rxbw.set_by_value(receiver_model.wfm_configuration());
break;
case MIC_MOD_AM:
rxbw.emplace_back("DSB 9k", 0); // we offer in AM DSB two audio BW 9k / 6k.
rxbw.emplace_back("DSB 6k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
case MIC_MOD_DSB:
rxbw.emplace_back("USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-SC case to the field_rxbw value.
rxbw.emplace_back("LSB-3k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
case MIC_MOD_USB:
rxbw.emplace_back("USB+3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
case MIC_MOD_LSB:
rxbw.emplace_back("LSB-3k", 0); // locked a fixed option, to display it.
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
}
}
void MicTXView::set_ptt_visibility(bool v) {
tx_button.hidden(!v);
void MicTXView::set_rxbw_defaults(bool use_app_settings) {
if (use_app_settings) {
field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
field_rxbw.set_by_value(rxbw_index);
} else if (mic_mod_index == MIC_MOD_NFM) {
field_bw.set_value(10); // NFM TX bw 10k, RX bw 16k (2) default
field_rxbw.set_by_value(2);
} else if (mic_mod_index == MIC_MOD_WFM) {
field_bw.set_value(75); // WFM TX bw 75K, RX bw 200k (0) default
field_rxbw.set_by_value(0);
}
// field_bw is hidden in other modulation cases
}
void MicTXView::update_receiver_rxbw(void) {
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
// In AM TX mode, we will allow both independent RX audio BW : AM 9K (9K00AE3 / AM 6K (6K00AE3). (In AM option v can be 0 (9k), 1 (6k)
// In DSB-SC TX mode, we will allow both independent RX SSB demodulation (USB / LSB side band). in that submenu, v is 0 (SSB1 USB) or 1 (SSB2 LSB)
switch (mic_mod_index) {
case MIC_MOD_NFM:
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
break;
case MIC_MOD_WFM:
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
break;
case MIC_MOD_AM:
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
break;
case MIC_MOD_USB:
case MIC_MOD_LSB:
break; // change can't happen in these modes because there's only one BW choice
case MIC_MOD_DSB:
receiver_model.set_am_configuration(rxbw_index + 2); // we are in DSB-SC TX mode, we need to select proper SSB filter. 0+2 =>usb(2), 1+2=3 =>lsb(3),
break;
}
}
MicTXView::MicTXView(
@@ -213,426 +309,317 @@ MicTXView::MicTXView(
baseband::run_image(portapack::spi_flash::image_tag_mic_tx);
if (audio::debug::codec_name() == "WM8731") {
add_children({&labels_WM8731, // we have audio codec WM8731, same MIC menu as original.
&vumeter,
&options_gain, // MIC GAIN float factor on the GUI.
&options_wm8731_boost_mode,
// &check_va,
&field_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&tx_view, // it already integrates previous rfgain, rfamp.
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&tx_button});
bool wm = (audio::debug::codec_name() == "WM8731");
add_children({&labels_both,
wm ? &labels_WM8731 : &labels_AK4951, // enable ALC if AK4951
&vumeter,
&options_gain, // MIC GAIN float factor on the GUI.
wm ? &options_wm8731_boost_mode : &options_ak4951_alc_mode,
&check_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&tx_view, // it already integrates previous rfgain, rfamp.
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_mic_to_HP, // check box to activate "hear mic"
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&tx_button,
&tx_icon});
} else {
add_children({&labels_AK4951, // we have audio codec AK4951, enable Automatic Level Control
&vumeter,
&options_gain,
&options_ak4951_alc_mode,
// &check_va,
&field_va,
&field_va_level,
&field_va_attack,
&field_va_decay,
&field_bw,
&tx_view, // it already integrates previous rfgain, rfamp.
&options_mode,
&field_frequency,
&options_tone_key,
&check_rogerbeep,
&check_common_freq_tx_rx, // added to handle common or separate freq- TX/RX
&check_rxactive,
&field_volume,
&field_rxbw,
&field_squelch,
&field_rxfrequency,
&field_rxlna,
&field_rxvga,
&field_rxamp,
&tx_button});
}
set_rxbw_options();
set_rxbw_defaults(settings_.loaded());
tone_keys_populate(options_tone_key);
options_tone_key.set_selected_index(tone_key_index);
options_tone_key.on_change = [this](size_t i, int32_t) {
tone_key_index = i;
};
options_tone_key.set_selected_index(0);
check_rogerbeep.set_value(rogerbeep_enabled);
check_rogerbeep.on_select = [this](Checkbox&, bool v) {
rogerbeep_enabled = v;
};
field_rxlna.set_value(receiver_model.lna());
field_rxlna.on_change = [this](int32_t v) {
receiver_model.set_lna(v);
};
field_rxvga.set_value(receiver_model.vga());
field_rxvga.on_change = [this](int32_t v) {
receiver_model.set_vga(v);
};
field_rxamp.set_value(receiver_model.rf_amp());
field_rxamp.on_change = [this](int32_t v) {
receiver_model.set_rf_amp(v);
};
options_gain.on_change = [this](size_t, int32_t v) {
mic_gain = v / 10.0;
mic_gain_x10 = v;
configure_baseband();
};
options_gain.set_selected_index(1); // x1.0 preselected default.
options_gain.set_by_value(mic_gain_x10); // x1.0 preselected default.
field_rxbw.on_change = [this](size_t, int32_t v) {
rxbw_index = v;
update_receiver_rxbw();
};
field_rxbw.set_by_value(rxbw_index);
field_squelch.set_value(receiver_model.squelch_level());
field_squelch.on_change = [this](int32_t v) {
receiver_model.set_squelch_level(v);
};
if (audio::debug::codec_name() == "WM8731") {
options_wm8731_boost_mode.on_change = [this](size_t, int8_t v) {
switch (v) {
case 0: // +12 dBs respect reference level orig fw 1.5.x fw FM : when +20dB's boost ON) and shift bits (>>8),
shift_bits_s16 = 6; // now mic-boost on (+20dBs) and shift bits (>>6), +20+12=32 dBs (orig fw +20 dBs+ 0dBs)=> +12dB's respect ref.
break;
case 1: // +06 dBs reference level , (when +20dB's boost ON)
shift_bits_s16 = 7; // now mic-boost on (+20dBs) and shift bits (>>7), +20+06=26 dBs (orig fw +20 dBs+ 0dBs) => +06dB's respect ref.
break;
case 2:
shift_bits_s16 = 4; // +04 dBs respect ref level , (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (4) (+0+24dB's)=24 dBs => +04dB's respect ref.
case 3:
shift_bits_s16 = 5; // -02 dBs respect ref level , (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (5) (+0+18dB's)=18 dBs => -02dB's respect ref.
case 4:
shift_bits_s16 = 6; // -08 dBs respect ref level , (when +20dB's boost OFF)
break; // now mic-boost off (+00dBs) shift bits (6) (+0+12dB's)=12 dBs => -08dB's respect ref.
}
ak4951_alc_and_wm8731_boost_GUI = v; // 0,..4 WM8731_boost dB's options, (combination boost on/off , and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (WM8731) , set up the proper wm_boost on/off , 0..4 (0,1) boost_on , (2,3,4) boost_0ff
configure_baseband(); // to update in real timme,sending msg , var-parameters >>shift_bits FM msg ,to audio_tx from M0 to M4 Proc -
ak4951_alc_and_wm8731_boost_GUI = v; // 0..4, WM8731_boost dB's options, (combination boost on/off, and effective gain in captured data >>x)
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (WM8731), set up the proper wm_boost on/off, 0..4 (0,1) boost_on, (2,3,4) boost_off,and the check box "Hear Mic"
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
};
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's .
if (!settings_.loaded()) {
ak4951_alc_and_wm8731_boost_GUI = 3; // preset GUI index 3 as default WM -> -02 dB's
}
options_wm8731_boost_mode.set_selected_index(ak4951_alc_and_wm8731_boost_GUI);
} else {
shift_bits_s16 = 8; // Initialized default fixed >>8_FM for FM tx mod , shift audio data for AK4951 ,using top 8 bits s16 data (>>8)
options_ak4951_alc_mode.on_change = [this](size_t, int8_t v) {
ak4951_alc_and_wm8731_boost_GUI = v; // 0,..11, AK4951 Mic -Automatic volume Level Control options,
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options
configure_baseband(); // sending fixed >>8_FM , var-parameters msg , to audiotx from this M0 to M4 process.
ak4951_alc_and_wm8731_boost_GUI = v; // 0..11, AK4951 Mic -Automatic volume Level Control options,
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // Detected (AK4951) ==> Set up proper ALC mode from 0..11 options, and the check box "Hear Mic"
configure_baseband(); // sending fixed >>8_FM, var-parameters msg, to audiotx from this M0 to M4 process.
};
if (!settings_.loaded())
ak4951_alc_and_wm8731_boost_GUI = 0;
options_ak4951_alc_mode.set_selected_index(ak4951_alc_and_wm8731_boost_GUI);
}
// options_ak4951_alc_mode.set_selected_index(0);
tx_frequency = transmitter_model.tuning_frequency();
field_frequency.set_value(transmitter_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step());
// WARNING: transmitter_model.set_target_frequency() and receiver_model.set_target_frequency() both update the same tuning freq, but one has an offset!
tx_frequency = transmitter_model.target_frequency();
field_frequency.on_change = [this](rf::Frequency f) {
tx_frequency = f;
if (!rx_enabled) { // not activated receiver. just update freq TX
transmitter_model.set_tuning_frequency(f);
} else { // activated receiver.
if (bool_same_F_tx_rx_enabled) // user selected common freq- TX = RX
receiver_model.set_tuning_frequency(f); // Update common freq also for RX
}
if (!rx_enabled)
transmitter_model.set_target_frequency(f);
else if (bool_same_F_tx_rx_enabled)
receiver_model.set_target_frequency(f);
// else tuning freq will be updated when rx is enabled, transmitting starts, or when rx_frequency is changed (if rx_enabled)
};
field_frequency.on_edit = [this, &nav]() {
focused_ui = 0;
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(tx_frequency);
new_view->on_changed = [this](rf::Frequency f) {
tx_frequency = f;
if (!rx_enabled) {
transmitter_model.set_tuning_frequency(f);
} else {
if (bool_same_F_tx_rx_enabled)
receiver_model.set_tuning_frequency(f); // Update freq also for RX
}
this->field_frequency.set_value(f);
set_dirty();
};
};
field_frequency.set_value(tx_frequency);
field_bw.on_change = [this](uint32_t v) {
transmitter_model.set_channel_bandwidth(v * 1000);
};
field_bw.set_value(10); // pre-default first time, TX deviation FM for NFM / FM
// TODO: would be nice if frequency step was configurable in this app
field_frequency.set_step(receiver_model.frequency_step());
// now , no need direct update , field_rfgain , field_rfamp (it is done in ui_transmitter.cpp)
options_mode.on_change = [this](size_t, int32_t v) { //{ "NFM/FM", 0 }, { " WFM ", 1 },{ "AM", 2 },{ "USB", 3 },{ "LSB", 4 },{ "DSB", 5 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
enable_wfm = false;
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t rxbw; // Aux structure to change dynamically field_rxbw contents,
switch (v) {
case 0: //{ "FM", 0 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
field_bw.set_value(10); // pre-default deviation FM for WFM
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
// if (rx_enabled)
rxaudio(rx_enabled); // Update now if we have RX audio on
options_tone_key.hidden(0); // we are in FM mode , we should have active the Key-tones & CTCSS option.
rxbw.emplace_back(" NFM1:8k5 ", 0); // restore the original dynamic field_rxbw value.
rxbw.emplace_back(" NFM2:11k ", 1);
rxbw.emplace_back(" FM :16k ", 2);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter , in non FM mode.
break;
case 1: //{ "WFM", 1 }
enable_am = false;
enable_usb = false;
enable_lsb = false;
enable_dsb = false;
enable_wfm = true;
field_bw.set_value(75); // pre-default deviation FM for WFM
// field_bw.set_value(transmitter_model.channel_bandwidth() / 1000);
// if (rx_enabled)
rxaudio(rx_enabled); // Update now if we have RX audio on
options_tone_key.hidden(0); // we are in WFM mode , we should have active the Key-tones & CTCSS option.
rxbw.emplace_back(" 200k-WFM ", 0); // We allow the user selection of the 3 x WFM BW filters, (0) WFM-200K, (1) WFM-180K , (2) WFM-40K .
rxbw.emplace_back(" 180k-WFM ", 1);
rxbw.emplace_back(" 40k-WFM ", 2);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter .
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter , in non FM mode.
break;
case 2: //{ "AM", 2 }
enable_am = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
options_tone_key.set_selected_index(0); // we are NOT in FM mode , we reset the possible previous key-tones &CTCSS selection.
set_dirty(); // Refresh display
options_tone_key.hidden(1); // we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
rxbw.emplace_back(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k .
rxbw.emplace_back(" DSB2-6k ", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
field_rxbw.hidden(0); // we show fixed RX AM BW 6Khz
field_bw.hidden(1); // we hide the FM TX deviation parameter , in non FM mode.
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
break;
case 3: //{ "USB", 3 }
enable_usb = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
rxbw.emplace_back(" USB+3k ", 0); // locked a fixed option , to display it .
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
set_dirty(); // Refresh display
break;
case 4: //{ "LSB", 4 }
enable_lsb = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB , by now.
check_rogerbeep.hidden(1); // hide that roger beep selection.
rxbw.emplace_back(" LSB-3k ", 0); // locked a fixed option , to display it .
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
set_dirty(); // Refresh display
break;
case 5: //{ "DSB-SC", 5 }
enable_dsb = true;
rxaudio(rx_enabled); // Update now if we have RX audio on
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
rxbw.emplace_back("SSB1:USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-C case to the field_rxbw value.
rxbw.emplace_back("SSB2:LSB-3k", 1);
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
break;
}
// configure_baseband();
};
/*
check_va.on_select = [this](Checkbox&, bool v) {
va_enabled = v;
text_ptt.hidden(v); //hide / show PTT text
check_rxactive.hidden(v); //hide / show the RX AUDIO
set_dirty(); //Refresh display
};
*/
field_va.set_selected_index(1);
field_va.on_change = [this](size_t, int32_t v) {
switch (v) {
case 0:
va_enabled = 0;
this->set_ptt_visibility(0);
check_rxactive.hidden(0);
ptt_enabled = 0;
break;
case 1:
va_enabled = 0;
this->set_ptt_visibility(1);
check_rxactive.hidden(0);
ptt_enabled = 1;
break;
case 2:
if (!rx_enabled) {
va_enabled = 1;
this->set_ptt_visibility(0);
check_rxactive.hidden(1);
ptt_enabled = 0;
} else {
field_va.set_selected_index(1);
}
break;
}
set_dirty();
};
check_rogerbeep.on_select = [this](Checkbox&, bool v) {
rogerbeep_enabled = v;
};
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
bool_same_F_tx_rx_enabled = v;
field_rxfrequency.hidden(v); // Hide or show separated freq RX field . (When no hide user can enter down indep. freq for RX)
set_dirty(); // Refresh GUI interface
receiver_model.set_tuning_frequency(v ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
};
field_va_level.on_change = [this](int32_t v) {
va_level = v;
vumeter.set_mark(v);
};
field_va_level.set_value(40);
field_va_attack.on_change = [this](int32_t v) {
attack_ms = v;
};
field_va_attack.set_value(500);
field_va_decay.on_change = [this](int32_t v) {
decay_ms = v;
};
field_va_decay.set_value(1000);
check_rxactive.on_select = [this](Checkbox&, bool v) {
// vumeter.set_value(0); //Start with a clean vumeter
rx_enabled = v;
// check_va.hidden(v); //Hide or show voice activation
rxaudio(v); // Activate-Deactivate audio rx accordingly
set_dirty(); // Refresh interface
};
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
field_rxbw.on_change = [this](size_t, int32_t v) {
if (!(enable_am || enable_usb || enable_lsb || enable_dsb || enable_wfm)) {
// In Previous fw versions, that nbfm_configuration(n) was done in any mode (FM/AM/SSB/DSB)...strictly speaking only need it in (NFM/FM)
receiver_model.set_nbfm_configuration(v); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter , NFM BW 8K5(0), NFM BW 11K(1) , FM BW 16K (2)
} else { // we are not in NFM/FM mode .(we could be in any of the rest : AM /USB/LSB/DSB-SC)
if (enable_am) { // we are in AM TX mode , we will allow both independent RX audio BW : AM 9K (9K00AE3 / AM 6K (6K00AE3). (In AM option v can be 0 (9k) , 1 (6k)
receiver_model.set_am_configuration(v); // we are in AM TX mode , we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
}
if (enable_dsb) { // we are in DSB-SC in TX mode , we will allow both independent RX SSB demodulation (USB / LSB side band). in that submenu, v is 0 (SSB1 USB) or 1 (SSB2 LSB)
receiver_model.set_am_configuration(v + 2); // we are in DSB-SC TX mode , we need to select proper SSB filter. 0+2 =>usb(2), 1+2=3 =>lsb(3),
}
if (enable_wfm) {
receiver_model.set_wfm_configuration(v); // we are in WFM case, we need to select proper WFB RX BW filter , WFM BW 200K(0), WFM BW 180K(1) , WFM BW 40K(2)
}
}
};
field_squelch.on_change = [this](int32_t v) {
receiver_model.set_squelch_level(100 - v);
};
field_squelch.set_value(0);
receiver_model.set_squelch_level(0);
rx_frequency = receiver_model.tuning_frequency();
// WARNING: transmitter_model.set_target_frequency() and receiver_model.set_target_frequency() both update the same tuning freq, but one has an offset!
field_rxfrequency.set_value(rx_frequency);
field_rxfrequency.set_step(receiver_model.frequency_step());
field_rxfrequency.on_change = [this](rf::Frequency f) { // available when field rxfrequency not hidden => user selected separated freq RX/TX-
rx_frequency = f;
if (rx_enabled)
receiver_model.set_tuning_frequency(f);
receiver_model.set_target_frequency(rx_frequency);
};
field_rxfrequency.on_edit = [this, &nav]() { // available when field rxfrequency not hidden => user selected separated freq RX/TX-
focused_ui = 1;
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(rx_frequency);
new_view->on_changed = [this](rf::Frequency f) {
rx_frequency = f;
if (rx_enabled)
receiver_model.set_tuning_frequency(f);
this->field_rxfrequency.set_value(f);
set_dirty();
};
};
rx_lna = receiver_model.lna();
field_rxlna.on_change = [this](int32_t v) {
rx_lna = v;
if (rx_enabled)
receiver_model.set_lna(v);
};
field_rxlna.set_value(rx_lna);
// TODO: would be nice if frequency step was configurable in this app
field_rxfrequency.set_step(receiver_model.frequency_step());
rx_vga = receiver_model.vga();
field_rxvga.on_change = [this](int32_t v) {
rx_vga = v;
if (rx_enabled)
receiver_model.set_vga(v);
check_common_freq_tx_rx.on_select = [this](Checkbox&, bool v) {
bool_same_F_tx_rx_enabled = v;
field_rxfrequency.hidden(v);
set_dirty();
receiver_model.set_target_frequency(bool_same_F_tx_rx_enabled ? tx_frequency : rx_frequency); // To go to the proper tuned freq. when toggling it
};
field_rxvga.set_value(rx_vga);
check_common_freq_tx_rx.set_value(bool_same_F_tx_rx_enabled);
rx_amp = receiver_model.rf_amp();
field_rxamp.on_change = [this](int32_t v) {
rx_amp = v;
if (rx_enabled)
receiver_model.set_rf_amp(rx_amp);
field_bw.on_change = [this](uint32_t v) {
transmitter_model.set_channel_bandwidth(v * 1000);
};
field_rxamp.set_value(rx_amp);
field_bw.set_value(transmitter_model.channel_bandwidth() / 1000); // pre-default first time, TX deviation FM for NFM / FM
// now, no need direct update, field_rfgain, field_rfamp (it is done in ui_transmitter.cpp)
options_mode.on_change = [this](size_t, int32_t v) {
mic_mod_index = v;
// update bw & tone key visibility - only visible in NFM/WFM modes
if ((mic_mod_index == MIC_MOD_NFM) || (mic_mod_index == MIC_MOD_WFM)) {
field_bw.hidden(false);
options_tone_key.hidden(false);
} else {
field_bw.hidden(true);
options_tone_key.set_selected_index(0);
options_tone_key.hidden(true);
}
// update rogerbeep visibility - disable in USB/LSB modes
if ((mic_mod_index == MIC_MOD_USB) || (mic_mod_index == MIC_MOD_LSB)) {
check_rogerbeep.set_value(false);
check_rogerbeep.hidden(true);
} else {
check_rogerbeep.hidden(false);
}
// update squelch visibility - only visible in NFM mode
field_squelch.hidden(mic_mod_index != MIC_MOD_NFM);
// update bandwidth
set_rxbw_options();
set_rxbw_defaults(false);
field_rxbw.hidden(false);
set_dirty(); // Refresh display
rxaudio(rx_enabled); // Update now if we have RX audio on
// configure_baseband();
};
options_mode.set_selected_index(mic_mod_index);
check_mic_to_HP.on_select = [this](Checkbox&, bool v) {
mic_to_HP_enabled = v;
if (mic_to_HP_enabled) { // When user click to "hear mic to HP", we will select the higher acoustic sound Option MODE ALC or BOOST-
audio::input::loopback_mic_to_hp_enable();
if (audio::debug::codec_name() == "WM8731") {
options_wm8731_boost_mode.set_selected_index(0); // In WM we always go to Boost +12 dBs respect reference level
} else if (ak4951_alc_and_wm8731_boost_GUI == 0) // In AK we are changing only that ALC index =0, because in that option, there is no sound.
options_ak4951_alc_mode.set_selected_index(1); // alc_mode =0 , means no ALC,no DIGITAL filter block (by passed), and that mode has no loopback mode.
} else {
audio::input::loopback_mic_to_hp_disable();
}
};
check_mic_to_HP.set_value(mic_to_HP_enabled);
check_rxactive.on_select = [this](Checkbox&, bool v) {
// vumeter.set_value(0); //Start with a clean vumeter
rx_enabled = v;
check_mic_to_HP.hidden(rx_enabled); // Toggle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
if ((rx_enabled) && (transmitting))
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
if (rx_enabled)
check_va.set_value(false); // Disallow voice activation during RX audio (for now) - Future TODO: Should allow VOX during RX audio
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
set_dirty(); // Refresh interface
};
check_rxactive.set_value(rx_enabled);
tx_button.on_select = [this](Button&) {
if (ptt_enabled && !transmitting) {
if (!transmitting) {
set_tx(true);
}
};
tx_button.on_touch_release = [this](Button&) {
if (button_touch) {
button_touch = false;
set_tx(false);
}
button_touch = false;
};
tx_button.on_touch_press = [this](Button&) {
if (!transmitting) {
button_touch = true;
}
button_touch = true;
};
field_va_level.on_change = [this](int32_t v) {
va_level = v;
vumeter.set_mark(v);
};
field_va_level.set_value(va_level);
field_va_attack.set_value(attack_ms);
field_va_attack.on_change = [this](int32_t v) {
attack_ms = v;
};
field_va_decay.set_value(decay_ms);
field_va_decay.on_change = [this](int32_t v) {
decay_ms = v;
};
check_va.on_select = [this](Checkbox&, bool v) {
va_enabled = v;
if (va_enabled)
check_rxactive.set_value(false); // Disallow RX-audio in VOX mode (for now) - Future TODO: Should allow VOX during RX audio
};
check_va.set_value(va_enabled);
// These shouldn't be necessary, but because
// this app uses both transmitter_model and directly
// configures the baseband, these end up being required.
transmitter_model.set_sampling_rate(sampling_rate);
transmitter_model.set_baseband_bandwidth(1750000);
set_tx(false);
audio::set_rate(audio::Rate::Hz_24000);
audio::input::start(ak4951_alc_and_wm8731_boost_GUI); // When detected AK4951 => set up ALC mode; when detected WM8731 => set up mic_boost ON/OFF.
audio::input::start(ak4951_alc_and_wm8731_boost_GUI, mic_to_HP_enabled); // set up ALC mode (AK4951) or set up mic_boost ON/OFF (WM8731). and the check box "Hear Mic"
// Workaround for bad RX reception when app first started -- shouldn't be necessary:
// Trigger receiver to update modulation.
if (rx_enabled)
receiver_model.set_squelch_level(receiver_model.squelch_level());
}
MicTXView::MicTXView(
NavigationView& nav,
ReceiverModel::settings_t override)
: MicTXView(nav) {
// Try to use the modulation/bandwidth from RX settings.
// TODO: These concepts should be merged so there's only one.
switch (override.mode) {
case ReceiverModel::Mode::AMAudio:
options_mode.set_selected_index(MIC_MOD_AM);
break;
case ReceiverModel::Mode::NarrowbandFMAudio:
options_mode.set_selected_index(MIC_MOD_NFM);
break;
case ReceiverModel::Mode::WidebandFMAudio:
options_mode.set_selected_index(MIC_MOD_WFM);
break;
// Unsupported modulations.
case ReceiverModel::Mode::SpectrumAnalysis:
case ReceiverModel::Mode::Capture:
default:
break;
}
check_common_freq_tx_rx.set_value(true); // freq passed from other app is in tx_frequency, so set rx_frequency=tx_frequency
// TODO: bandwidth selection is tied too tightly to the UI
// controls. It's not possible to set the bandwidth here without
// refactoring. Also options_mode seems to have a category error.
}
MicTXView::~MicTXView() {
audio::input::stop();
transmitter_model.set_tuning_frequency(tx_frequency); // Save Tx frequency instead of Rx. Or maybe we need some "System Wide" changes to seperate Tx and Rx frequency.
transmitter_model.disable();
if (rx_enabled) // Also turn off audio rx if enabled
if (rx_enabled) { // Also turn off both (audio rx if enabled, and disable mic_loop to HP)
rxaudio(false);
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
audio::input::loopback_mic_to_hp_disable(); // Leave Mic audio off in the main menu (as original audio path, otherwise we had No audio in next "Audio App")
}
transmitter_model.set_target_frequency(tx_frequency); // Set tx_frequency here to be saved in app_settings (might not match rx_frequency)
transmitter_model.disable();
baseband::shutdown();
}
} // namespace ui
+134 -120
View File
@@ -20,18 +20,18 @@
* Boston, MA 02110-1301, USA.
*/
// TODO: Consolidate Modulation/Bandwidth modes/settings with freqman/receiver_model.
#ifndef __UI_MICTX_H__
#define __UI_MICTX_H__
#define SHORT_UI true
#define NORMAL_UI false
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "transmitter_model.hpp"
#include "tone_key.hpp"
#include "message.hpp"
#include "receiver_model.hpp"
#include "ui_transmitter.hpp"
@@ -41,6 +41,7 @@ namespace ui {
class MicTXView : public View {
public:
MicTXView(NavigationView& nav);
MicTXView(NavigationView& nav, ReceiverModel::settings_t override);
~MicTXView();
MicTXView(const MicTXView&) = delete;
@@ -69,86 +70,101 @@ class MicTXView : public View {
static constexpr uint32_t lcd_frame_duration = (256 * 1000UL) / 60; // 1 frame @ 60fps in ms .8 fixed point /60
void update_vumeter();
bool tx_button_held();
void do_timing();
void set_tx(bool enable);
// void on_tuning_frequency_changed(rf::Frequency f);
void on_tx_progress(const bool done);
void update_tx_icon();
uint8_t shift_bits(void);
void configure_baseband();
void set_rxbw_options(void);
void set_rxbw_defaults(bool use_app_settings);
void update_receiver_rxbw(void);
void rxaudio(bool is_on);
void on_headphone_volume_changed(int32_t v);
void set_ptt_visibility(bool v);
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
sampling_rate /* sampling rate */
};
bool transmitting{false};
enum Mic_Modulation : uint32_t {
MIC_MOD_NFM = 0,
MIC_MOD_WFM = 1,
MIC_MOD_AM = 2,
MIC_MOD_USB = 3,
MIC_MOD_LSB = 4,
MIC_MOD_DSB = 5,
};
// Settings
uint32_t mic_mod_index{0};
uint32_t rxbw_index{0};
bool va_enabled{false};
bool ptt_enabled{true};
bool rogerbeep_enabled{false};
bool mic_to_HP_enabled{false};
bool bool_same_F_tx_rx_enabled{false};
rf::Frequency rx_frequency{0};
bool rx_enabled{false};
uint32_t tone_key_index{};
float mic_gain{1.0};
uint32_t tone_key_index{0};
uint32_t mic_gain_x10{1};
uint8_t ak4951_alc_and_wm8731_boost_GUI{0};
uint32_t va_level{40};
uint32_t attack_ms{500};
uint32_t decay_ms{1000};
app_settings::SettingsManager settings_{
"tx_mic",
app_settings::Mode::RX_TX,
app_settings::Options::UseGlobalTargetFrequency,
{
{"mic_mod_index"sv, &mic_mod_index},
{"rxbw_index"sv, &rxbw_index},
{"same_F_tx_rx"sv, &bool_same_F_tx_rx_enabled},
{"mic_rx_frequency"sv, &rx_frequency},
{"rx_enabled"sv, &rx_enabled},
{"mic_gain_x10"sv, &mic_gain_x10},
{"mic_to_HP"sv, &mic_to_HP_enabled},
{"alc_and_boost"sv, &ak4951_alc_and_wm8731_boost_GUI},
{"va_level"sv, &va_level},
{"attack_ms"sv, &attack_ms},
{"decay_ms"sv, &decay_ms},
{"vox"sv, &va_enabled},
{"rogerbeep"sv, &rogerbeep_enabled},
{"tone_key_index"sv, &tone_key_index},
}};
rf::Frequency tx_frequency{0};
bool transmitting{false};
uint32_t audio_level{0};
uint32_t va_level{};
uint32_t attack_ms{};
uint32_t decay_ms{};
uint32_t attack_timer{0};
uint32_t decay_timer{0};
int32_t tx_gain{47};
bool rf_amp{false};
int32_t rx_lna{32};
int32_t rx_vga{32};
bool rx_amp{false};
rf::Frequency tx_frequency{0};
rf::Frequency rx_frequency{0};
int32_t focused_ui{2};
bool button_touch{false};
uint8_t shift_bits_s16{4}; // shift bits factor to the captured ADC S16 audio sample.
// AM TX Stuff
bool enable_am{false};
bool enable_dsb{false};
bool enable_usb{false};
bool enable_lsb{false};
bool enable_wfm{false}; // added to distinguish in the FM mode , RX BW : NFM (8K5, 11K), FM (16K), WFM(200K)
Labels labels_WM8731{
Labels labels_both{
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()},
{{3 * 8, 3 * 8}, "F:", Color::light_grey()},
{{15 * 8, 3 * 8}, "FM TXBW: kHz", Color::light_grey()}, // to be more symetric and consistent to the below FM RXBW
{{18 * 8, (5 * 8)}, "Mode:", Color::light_grey()}, // now , no need to handle GAIN , Amp here It is handled by ui_transmitter.cpp
{{3 * 8, 8 * 8}, "TX Activation:", Color::light_grey()}, // we delete { { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
{{18 * 8, (5 * 8)}, "Mode:", Color::light_grey()}, // now, no need to handle GAIN, Amp here It is handled by ui_transmitter.cpp
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
{{4 * 8, (13 * 8) - 2}, "TONE KEY:", Color::light_grey()},
{{7 * 8, 23 * 8}, "VOL:", Color::light_grey()},
{{14 * 8, 23 * 8}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, 25 * 8}, "SQ:", Color::light_grey()},
{{5 * 8, 25 * 8}, "F_RX:", Color::light_grey()},
{{5 * 8, 27 * 8}, "LNA:", Color::light_grey()},
{{12 * 8, 27 * 8}, "VGA:", Color::light_grey()},
{{19 * 8, 27 * 8}, "AMP:", Color::light_grey()}};
{{3 * 8, (13 * 8) - 5}, "TONE KEY:", Color::light_grey()},
{{3 * 8, (18 * 8) - 1}, "======== Receiver ========", Color::green()},
{{5 * 8, (23 * 8) + 2}, "VOL:", Color::light_grey()},
{{14 * 8, (23 * 8) + 2}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, (25 * 8) + 2}, "SQ:", Color::light_grey()},
{{5 * 8, (25 * 8) + 2}, "F_RX:", Color::light_grey()},
{{5 * 8, (27 * 8) + 2}, "LNA:", Color::light_grey()},
{{12 * 8, (27 * 8) + 2}, "VGA:", Color::light_grey()},
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()}};
Labels labels_WM8731{
{{17 * 8, 1 * 8}, "Boost", Color::light_grey()}};
Labels labels_AK4951{
{{3 * 8, 1 * 8}, "MIC-GAIN:", Color::light_grey()},
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()},
{{3 * 8, 3 * 8}, "F:", Color::light_grey()},
{{15 * 8, 3 * 8}, "FM TXBW: kHz", Color::light_grey()},
{{18 * 8, (5 * 8)}, "Mode:", Color::light_grey()}, // now , no need to handle GAIN , Amp here It is handled by ui_transmitter.cpp
{{3 * 8, 8 * 8}, "TX Activation:", Color::light_grey()}, // we delete { { 3 * 8, 5 * 8 }, "GAIN:", Color::light_grey() },
{{4 * 8, 10 * 8}, "LVL:", Color::light_grey()}, // we delete { {11 * 8, 5 * 8 }, "Amp:", Color::light_grey() },
{{12 * 8, 10 * 8}, "ATT:", Color::light_grey()},
{{20 * 8, 10 * 8}, "DEC:", Color::light_grey()},
{{4 * 8, (13 * 8) - 2}, "TONE KEY:", Color::light_grey()},
{{(6 * 8) + 4, 23 * 8}, "VOL:", Color::light_grey()},
{{14 * 8, 23 * 8}, "RXBW:", Color::light_grey()}, // we remove the label "FM" because we will display all MOD types RX_BW.
{{20 * 8, 25 * 8}, "SQ:", Color::light_grey()},
{{5 * 8, 25 * 8}, "F_RX:", Color::light_grey()},
{{5 * 8, 27 * 8}, "LNA:", Color::light_grey()},
{{12 * 8, 27 * 8}, "VGA:", Color::light_grey()},
{{19 * 8, 27 * 8}, "AMP:", Color::light_grey()}};
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()}};
VuMeter vumeter{
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
@@ -164,15 +180,15 @@ class MicTXView : public View {
{"x2.0", 20}}};
OptionsField options_ak4951_alc_mode{
{20 * 8, 1 * 8}, // Coordinates are: int:x (px), int:y (px)
11,
{20 * 8, 1 * 8},
10, // Label has 10 chars
{
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL,keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL, keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
{"+12dB-6kHz", 1}, // ALC-> on, (+12dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+09dB-6kHz", 2}, // ALC-> on, (+09dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+06dB-6kHz", 3}, // ALC-> on, (+06dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+03dB-2kHz", 4}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 3,5k + Pre-amp Mic (+21dB=original)+ EQ boosting ~<2kHz (f0~1k1,fb:1,7K, k=1,8)
{"+03dB-4kHz", 5}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 4kHz + Pre-amp Mic (+21dB=original)+ EQ boosting ~<3kHz (f0~1k4,fb~2,4k, k=1,8)
{"+03dB-2kHz", 4}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 3,5k + Pre-amp Mic (+21dB=original)+ EQ boosting ~<2kHz (f0~1k1, fb:1,7K, k=1,8)
{"+03dB-4kHz", 5}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 4kHz + Pre-amp Mic (+21dB=original)+ EQ boosting ~<3kHz (f0~1k4, fb~2,4k, k=1,8)
{"+03dB-6kHz", 6}, // ALC-> on, (+03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"+00dB-6kHz", 7}, // ALC-> on, (+00dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
{"-03dB-6kHz", 8}, // ALC-> on, (-03dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
@@ -182,16 +198,17 @@ class MicTXView : public View {
}};
OptionsField options_wm8731_boost_mode{
{22 * 8, 1 * 8}, // Coordinates are: int:x (px), int:y (px)
5,
{22 * 8, 1 * 8},
8, // Label has 8 chars
{
{"ON +12dB", 0}, // WM8731 Mic Boost ON ,original+12dBs condition, easy to saturate ADC sat in high voice ,relative G = +12 dB's respect ref level
{"ON +06dB", 1}, // WM8731 Mic Boost ON ,original+6 dBs condition, easy to saturate ADC sat in high voice ,relative G = +06 dB's respect ref level
{"OFF+04dB", 2}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = +04 dB's (respect ref level) , always effective sampling 24khz
{"OFF-02dB", 3}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = -02 dB's (respect ref level)
{"OFF-08dB", 4}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice ,relative G = -12 dB's (respect ref level)
{"ON +12dB", 0}, // WM8731 Mic Boost ON, original+12dBs condition, easy to saturate ADC sat in high voice, relative G = +12 dB's respect ref level
{"ON +06dB", 1}, // WM8731 Mic Boost ON, original+6 dBs condition, easy to saturate ADC sat in high voice, relative G = +06 dB's respect ref level
{"OFF+04dB", 2}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = +04 dB's (respect ref level), always effective sampling 24khz
{"OFF-02dB", 3}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = -02 dB's (respect ref level)
{"OFF-08dB", 4}, // WM8731 Mic Boost OFF to avoid ADC sat in high voice, relative G = -12 dB's (respect ref level)
}};
// TODO: Use TxFrequencyField
FrequencyField field_frequency{
{5 * 8, 3 * 8},
};
@@ -203,37 +220,26 @@ class MicTXView : public View {
' '};
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
// 3*8, 2*8, SHORT_UI // x(columns), y (line) position. (used in Replay / GPS Simul / Playlist App's)
3 * 8, 2 * 8, NORMAL_UI // x(columns), y (line) position. (used in Mic App)
};
{3 * 8, 5 * 8},
/*short_ui*/ false};
OptionsField options_mode{
{24 * 8, 5 * 8},
4,
6,
{
{"NFM/FM", 0},
{" WFM ", 1},
{" AM ", 2}, // in fact that TX mode = AM -DSB with carrier .
{" USB ", 3},
{" LSB ", 4},
{"DSB-SC", 5} // We are TX Double Side AM Band with suppressed carrier, and allowing in RX both indep SSB lateral band (USB/LSB).
{"NFM/FM", MIC_MOD_NFM},
{" WFM ", MIC_MOD_WFM},
{" AM ", MIC_MOD_AM}, // in fact that TX mode = AM -DSB with carrier.
{" USB ", MIC_MOD_USB},
{" LSB ", MIC_MOD_LSB},
{"DSB-SC", MIC_MOD_DSB} // We are TX Double Side AM Band with suppressed carrier, and allowing in RX both indep SSB lateral band (USB/LSB).
}};
/*
Checkbox check_va {
{ 3 * 8, (10 * 8) - 4 },
7,
"Voice activation",
false
};
*/
OptionsField field_va{
{17 * 8, 8 * 8},
3,
{{" OFF", 0},
{" PTT", 1},
{"AUTO", 2}}};
Checkbox check_va{
{3 * 8, 8 * 7},
10,
"VOX enable",
false};
NumberField field_va_level{
{8 * 8, 10 * 8},
@@ -255,59 +261,61 @@ class MicTXView : public View {
' '};
OptionsField options_tone_key{
{10 * 8, (15 * 8) - 2},
23,
{12 * 8, (13 * 8) - 5},
18,
{}};
Checkbox check_rogerbeep{
{3 * 8, (16 * 8) + 7},
{3 * 8, (14 * 8) + 4},
10,
"Roger beep",
false};
Checkbox check_mic_to_HP{
{18 * 8, (14 * 8) + 4},
10,
"Hear Mic",
false};
Checkbox check_rxactive{
{3 * 8, (21 * 8) - 4},
18, // it was 8, but if it is string size should be 18
"RX audio listening",
{3 * 8, (21 * 8) - 7},
8, // it was 18, but if it is string size should be 8
"RX audio",
false};
Checkbox check_common_freq_tx_rx{
{18 * 8, (16 * 8) + 7},
{18 * 8, (21 * 8) - 7},
8,
"F = F_RX",
"F RX=TX",
false};
NumberField field_volume{
{11 * 8, 23 * 8},
2,
{0, 99},
1,
' ',
};
AudioVolumeField field_volume{
{9 * 8, (23 * 8) + 2}};
OptionsField field_rxbw{
{19 * 8, 23 * 8},
3,
{19 * 8, (23 * 8) + 2},
7,
{
{" NFM1:8k5 ", 0},
{" NFM2:11k ", 1},
{" FM :16k ", 2},
{" 8k5 ", 0}, // Initial dynamic values when we start Mic App.
{" 11k ", 1},
{" 16k ", 2},
}};
NumberField field_squelch{
{23 * 8, 25 * 8},
{23 * 8, (25 * 8) + 2},
2,
{0, 99},
1,
' ',
};
// TODO: Use RxFrequencyField
FrequencyField field_rxfrequency{
{10 * 8, 25 * 8},
{10 * 8, (25 * 8) + 2},
};
NumberField field_rxlna{
{9 * 8, 27 * 8},
{9 * 8, (27 * 8) + 2},
2,
{0, 40},
8,
@@ -315,7 +323,7 @@ class MicTXView : public View {
};
NumberField field_rxvga{
{16 * 8, 27 * 8},
{16 * 8, (27 * 8) + 2},
2,
{0, 62},
2,
@@ -323,7 +331,7 @@ class MicTXView : public View {
};
NumberField field_rxamp{
{24 * 8, 27 * 8},
{24 * 8, (27 * 8) + 2},
1,
{0, 1},
1,
@@ -332,9 +340,15 @@ class MicTXView : public View {
Button tx_button{
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
"TX",
"PTT TX",
true};
Image tx_icon{
{6 * 8, 31 * 8 + 4, 16, 16},
&bitmap_icon_microphone,
Color::black(),
Color::black()};
MessageHandlerRegistration message_handler_lcd_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
+23 -25
View File
@@ -23,7 +23,6 @@
#include "ui_modemsetup.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
@@ -41,15 +40,20 @@ ModemSetupView::ModemSetupView(
using options_t = std::vector<option_t>;
options_t modem_options;
add_children({&labels,
&field_baudrate,
&field_mark,
&field_space,
&field_repeat,
&options_modem,
&button_set_modem,
&sym_format,
&button_save});
add_children({
&labels,
&field_baudrate,
&field_mark,
&field_space,
&field_repeat,
&options_modem,
&button_set_modem,
&sym_format_data,
&sym_format_parity,
&sym_format_stop,
&sym_format_msb,
&button_save,
});
// Only list AFSK modems for now
for (size_t i = 0; i < MODEM_DEF_COUNT; i++) {
@@ -59,15 +63,10 @@ ModemSetupView::ModemSetupView(
options_modem.set_options(modem_options);
options_modem.set_selected_index(0);
sym_format.set_symbol_list(0, "6789"); // Data bits
sym_format.set_symbol_list(1, "NEo"); // Parity
sym_format.set_symbol_list(2, "012"); // Stop bits
sym_format.set_symbol_list(3, "ML"); // MSB/LSB first
sym_format.set_sym(0, persistent_memory::serial_format().data_bits - 6);
sym_format.set_sym(1, persistent_memory::serial_format().parity);
sym_format.set_sym(2, persistent_memory::serial_format().stop_bits);
sym_format.set_sym(3, persistent_memory::serial_format().bit_order);
sym_format_data.set_offset(0, persistent_memory::serial_format().data_bits - 6);
sym_format_parity.set_offset(0, persistent_memory::serial_format().parity);
sym_format_stop.set_offset(0, persistent_memory::serial_format().stop_bits);
sym_format_msb.set_offset(0, persistent_memory::serial_format().bit_order);
field_mark.set_value(persistent_memory::afsk_mark_freq());
field_space.set_value(persistent_memory::afsk_space_freq());
@@ -84,18 +83,17 @@ ModemSetupView::ModemSetupView(
};
button_save.on_select = [this, &nav](Button&) {
serial_format_t serial_format;
persistent_memory::set_afsk_mark(field_mark.value());
persistent_memory::set_afsk_space(field_space.value());
persistent_memory::set_modem_baudrate(field_baudrate.value());
persistent_memory::set_modem_repeat(field_repeat.value());
serial_format.data_bits = sym_format.get_sym(0) + 6;
serial_format.parity = (parity_enum)sym_format.get_sym(1);
serial_format.stop_bits = sym_format.get_sym(2);
serial_format.bit_order = (order_enum)sym_format.get_sym(3);
serial_format_t serial_format{};
serial_format.data_bits = sym_format_data.get_offset(0) + 6;
serial_format.parity = (parity_enum)sym_format_parity.get_offset(0);
serial_format.stop_bits = sym_format_stop.get_offset(0);
serial_format.bit_order = (order_enum)sym_format_msb.get_offset(0);
persistent_memory::set_serial_format(serial_format);
+18 -5
View File
@@ -38,8 +38,6 @@ class ModemSetupView : public View {
std::string title() const override { return "Modem setup"; };
private:
void update_freq(rf::Frequency f);
Labels labels{
{{2 * 8, 11 * 8}, "Baudrate:", Color::light_grey()},
{{2 * 8, 13 * 8}, "Mark: Hz", Color::light_grey()},
@@ -81,10 +79,25 @@ class ModemSetupView : public View {
7,
{}};
SymField sym_format{
SymField sym_format_data{
{16 * 8, 22 * 8},
4,
SymField::SYMFIELD_DEF};
1,
"6789"};
SymField sym_format_parity{
{17 * 8, 22 * 8},
1,
"NEo"};
SymField sym_format_stop{
{18 * 8, 22 * 8},
1,
"012"};
SymField sym_format_msb{
{19 * 8, 22 * 8},
1,
"ML"};
Button button_set_modem{
{23 * 8, 6 * 8 - 4, 6 * 8, 24},
+48 -44
View File
@@ -26,7 +26,6 @@
#include "baseband_api.hpp"
#include "hackrf_gpio.hpp"
#include "portapack_shared_memory.hpp"
#include "cpld_update.hpp"
#include "ui_textentry.hpp"
#include "string_format.hpp"
@@ -57,13 +56,23 @@ static msg_t ookthread_fn(void* arg) {
v = (symbol < 2) ? 1 : 0; // TX on for dot or dash, off for pause
delay = morse_symbols[symbol];
gpio_og_tx.write(v);
if (hackrf_r9) { // r9 has a common PIN 54 for MODE CONTROL TX / RX, <=r6 has two independent MODE CONTROL pins (TX and RX)
gpio_r9_rx.write(!v); // in hackrf r9 opposite logic "0" means tx , "1" rx = no tx)
} else {
gpio_og_tx.write(v); // in hackrf <=r6, "1" means tx , "0" no tx.
}
arg_c->on_tx_progress(i, false);
chThdSleepMilliseconds(delay * arg_c->time_unit_ms);
}
gpio_og_tx.write(0); // Ensure TX is off
if (hackrf_r9) { // r9 has a common PIN 54 for MODE CONTROL TX / RX, <=r6 has two independent MODE CONTROL pins (TX and RX)
gpio_r9_rx.write(1); // Hackrf_r9 , common pin, rx="1" (we ensure TX is off) / tx="0"
} else {
gpio_og_tx.write(0); // Hackrf <=r6 independent TX / RX pins ,now touching the tx pin ==> Ensure TX is off
}
arg_c->on_tx_progress(0, true);
chThdExit(0);
@@ -98,13 +107,8 @@ void MorseView::focus() {
}
MorseView::~MorseView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_morse", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit .
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void MorseView::paint(Painter&) {
@@ -123,15 +127,14 @@ bool MorseView::start_tx() {
}
progressbar.set_max(symbol_count);
transmitter_model.set_sampling_rate(1536000U);
transmitter_model.set_baseband_bandwidth(1750000);
// By experimental spectrum measurment, we left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min).
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF .already tested in FM morse max tone 9,999k , max dev 150khz
transmitter_model.enable();
if (modulation == CW) {
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
if (mode_cw) {
ookthread = chThdCreateStatic(ookthread_wa, sizeof(ookthread_wa), NORMALPRIO + 10, ookthread_fn, this);
} else if (modulation == FM) {
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
}
return true;
@@ -140,8 +143,8 @@ bool MorseView::start_tx() {
void MorseView::update_tx_duration() {
uint32_t duration_ms;
time_unit_ms = 1200 / field_speed.value();
symbol_count = morse_encode(message, time_unit_ms, field_tone.value(), &time_units);
time_unit_ms = 1200 / speed;
symbol_count = morse_encode(message, time_unit_ms, tone, &time_units);
if (symbol_count) {
duration_ms = time_units * time_unit_ms;
@@ -182,7 +185,7 @@ void MorseView::on_loop_progress(const uint32_t progress, const bool done) {
}
void MorseView::set_foxhunt(size_t i) {
message = foxhunt_codes[i];
message = foxhunt_codes[i - 1];
buffer = message.c_str();
text_message.set(message);
update_tx_duration();
@@ -195,7 +198,7 @@ MorseView::MorseView(
add_children({&labels,
&checkbox_foxhunt,
&options_foxhunt,
&field_foxhunt,
&field_speed,
&field_tone,
&options_modulation,
@@ -206,54 +209,55 @@ MorseView::MorseView(
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_morse", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
// Default settings
field_speed.set_value(15); // 15wps
field_tone.set_value(700); // 700Hz FM tone
options_modulation.set_selected_index(0); // CW mode
options_loop.set_selected_index(0); // Off
field_speed.set_value(speed);
field_tone.set_value(tone);
options_modulation.set_by_value(mode_cw);
options_loop.set_by_value(loop);
checkbox_foxhunt.set_value(foxhunt_mode);
field_foxhunt.set_value(foxhunt_code);
checkbox_foxhunt.on_select = [this](Checkbox&, bool value) {
foxhunt_mode = value;
if (foxhunt_mode)
set_foxhunt(options_foxhunt.selected_index_value());
set_foxhunt(foxhunt_code);
};
options_foxhunt.on_change = [this](size_t i, int32_t) {
field_foxhunt.on_change = [this](int32_t value) {
foxhunt_code = value;
if (foxhunt_mode)
set_foxhunt(i);
set_foxhunt(foxhunt_code);
};
options_modulation.on_change = [this](size_t i, int32_t) {
modulation = (modulation_t)i;
};
options_loop.on_change = [this](size_t i, uint32_t n) {
options_modulation.on_change = [this](size_t i, int32_t value) {
(void)i; // avoid unused warning
loop = n;
mode_cw = (bool)value;
};
field_speed.on_change = [this](int32_t) {
options_loop.on_change = [this](size_t i, uint32_t value) {
(void)i; // avoid unused warning
loop = value;
};
field_speed.on_change = [this](int32_t value) {
speed = value;
update_tx_duration();
};
field_tone.on_change = [this](int32_t value) {
tone = value;
};
button_message.on_select = [this, &nav](Button&) {
this->on_set_text(nav);
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+31 -25
View File
@@ -28,6 +28,7 @@
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "portapack.hpp"
#include "message.hpp"
#include "volume.hpp"
@@ -64,19 +65,33 @@ class MorseView : public View {
private:
NavigationView& nav_;
std::string buffer{"PORTAPACK"};
std::string message{};
uint32_t time_units{0};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
enum modulation_t {
CW = 0,
FM = 1
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
modulation_t modulation{CW};
std::string buffer{"PORTAPACK"};
bool mode_cw{true};
bool foxhunt_mode{false};
uint32_t foxhunt_code{1};
uint32_t speed{15};
uint32_t tone{700};
app_settings::SettingsManager settings_{
"tx_morse",
app_settings::Mode::TX,
{
{"message"sv, &buffer},
{"foxhunt"sv, &foxhunt_mode},
{"foxhunt_code"sv, &foxhunt_code},
{"speed"sv, &speed},
{"tone"sv, &tone},
{"mode_cw"sv, &mode_cw},
{"loop"sv, &loop},
}};
bool start_tx();
void update_tx_duration();
@@ -85,7 +100,6 @@ class MorseView : public View {
Thread* ookthread{nullptr};
Thread* loopthread{nullptr};
bool foxhunt_mode{false};
bool run{false};
Labels labels{
@@ -99,20 +113,12 @@ class MorseView : public View {
{4 * 8, 16},
8,
"Foxhunt:"};
OptionsField options_foxhunt{
NumberField field_foxhunt{
{17 * 8, 16 + 4},
7,
{{"1 (MOE)", 0},
{"2 (MOI)", 1},
{"3 (MOS)", 2},
{"4 (MOH)", 3},
{"5 (MO5)", 4},
{"6 (MON)", 5},
{"7 (MOD)", 6},
{"8 (MOB)", 7},
{"9 (MO6)", 8},
{"X (MO) ", 9},
{"T (S) ", 10}}};
2,
{1, 11},
1,
' '};
NumberField field_speed{
{10 * 8, 6 * 8},
@@ -131,8 +137,8 @@ class MorseView : public View {
OptionsField options_modulation{
{15 * 8, 10 * 8},
2,
{{"CW", 0},
{"FM", 1}}};
{{"CW", true},
{"FM", false}}};
OptionsField options_loop{
{9 * 8, 12 * 8},
+19 -31
View File
@@ -21,6 +21,7 @@
*/
#include "ui_numbers.hpp"
#include "ui_styles.hpp"
#include "string_format.hpp"
#include "portapack.hpp"
@@ -38,7 +39,7 @@ namespace ui {
void NumbersStationView::focus() {
if (file_error)
nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT, nullptr);
nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT);
else
button_exit.focus();
}
@@ -75,7 +76,7 @@ void NumbersStationView::prepare_audio() {
return;
}
code = symfield_code.get_sym(code_index);
code = symfield_code.get_offset(code_index);
if (code >= 10) {
memset(audio_buffer, 0, 1024);
@@ -129,9 +130,7 @@ void NumbersStationView::start_tx() {
prepare_audio();
transmitter_model.set_sampling_rate(1536000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
baseband::set_audiotx_data(
@@ -146,7 +145,7 @@ void NumbersStationView::on_tick_second() {
armed_blink = not armed_blink;
if (armed_blink)
check_armed.set_style(&style_red);
check_armed.set_style(&Styles::red);
else
check_armed.set_style(&style());
@@ -154,14 +153,12 @@ void NumbersStationView::on_tick_second() {
}
void NumbersStationView::on_voice_changed(size_t index) {
std::string code_list = "";
std::string code_list;
for (const auto& wavs : voices[index].available_wavs)
code_list += wavs.code;
for (uint32_t c = 0; c < 25; c++)
symfield_code.set_symbol_list(c, code_list);
symfield_code.set_symbol_list(code_list);
current_voice = &voices[index];
}
@@ -267,31 +264,22 @@ NumbersStationView::NumbersStationView(
};
// DEBUG
symfield_code.set_sym(0, 10);
symfield_code.set_sym(1, 3);
symfield_code.set_sym(2, 4);
symfield_code.set_sym(3, 11);
symfield_code.set_sym(4, 6);
symfield_code.set_sym(5, 1);
symfield_code.set_sym(6, 9);
symfield_code.set_sym(7, 7);
symfield_code.set_sym(8, 8);
symfield_code.set_sym(9, 0);
symfield_code.set_sym(10, 12); // End
symfield_code.set_offset(0, 10);
symfield_code.set_offset(1, 3);
symfield_code.set_offset(2, 4);
symfield_code.set_offset(3, 11);
symfield_code.set_offset(4, 6);
symfield_code.set_offset(5, 1);
symfield_code.set_offset(6, 9);
symfield_code.set_offset(7, 7);
symfield_code.set_offset(8, 8);
symfield_code.set_offset(9, 0);
symfield_code.set_offset(10, 12); // End
/*
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
// Thanks, Sakamoto-sama !
y = datetime.year();
m = datetime.month();
d = datetime.day();
y -= m < 3;
dayofweek = (y + y/4 - y/100 + y/400 + month_table[m-1] + d) % 7;
dayofweek = rtc_time::current_day_of_week();
text_title.set(day_of_week[dayofweek]);
*/
*/
button_exit.on_select = [&nav](Button&) {
nav.pop();
+15 -11
View File
@@ -27,7 +27,6 @@
#include "ui_widget.hpp"
#include "ui_receiver.hpp"
#include "ui_navigation.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "rtc_time.hpp"
#include "clock_manager.hpp"
#include "baseband_api.hpp"
@@ -35,6 +34,7 @@
#include "message.hpp"
#include "file.hpp"
#include "io_wave.hpp"
#include "radio_state.hpp"
namespace ui {
@@ -55,6 +55,12 @@ class NumbersStationView : public View {
private:
NavigationView& nav_;
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
// Sequencing state machine
enum segments {
IDLE = 0,
@@ -63,11 +69,6 @@ class NumbersStationView : public View {
SIGNOFF
};
Style style_red{
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::red()};
typedef struct {
char code;
uint32_t index;
@@ -142,16 +143,19 @@ class NumbersStationView : public View {
SymField symfield_code{
{1 * 8, 10 * 8},
25,
SymField::SYMFIELD_DEF};
SymField::Type::Custom};
Checkbox check_armed{
{2 * 8, 13 * 16},
5,
"Armed"};
/*Button button_tx_now {
{ 18 * 8, 13 * 16, 10 * 8, 32 },
"TX now"
};*/
/*
Button button_tx_now {
{ 18 * 8, 13 * 16, 10 * 8, 32 },
"TX now"};
*/
Button button_exit{
{21 * 8, 16 * 16, 64, 32},
"Exit"};
+3 -6
View File
@@ -77,11 +77,7 @@ void NuoptixView::transmit(bool setup) {
timecode = number_timecode.value();
}
transmitter_model.set_sampling_rate(1536000U);
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
@@ -138,6 +134,7 @@ void NuoptixView::transmit(bool setup) {
shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
audio::set_rate(audio::Rate::Hz_24000);
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
timecode++;
@@ -156,9 +153,9 @@ NuoptixView::NuoptixView(
number_timecode.set_value(1);
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+7 -2
View File
@@ -25,7 +25,6 @@
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "baseband_api.hpp"
#include "ui_navigation.hpp"
#include "ui_transmitter.hpp"
@@ -34,6 +33,7 @@
#include "message.hpp"
#include "volume.hpp"
#include "audio.hpp"
#include "radio_state.hpp"
#define NUOPTIX_TONE_LENGTH ((TONES_SAMPLERATE * 0.049) - 1) // 49ms
@@ -55,9 +55,14 @@ class NuoptixView : public View {
IMPROVISE
};
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
tx_modes tx_mode{IDLE};
void on_tuning_frequency_changed(rf::Frequency f);
void transmit(bool setup);
void on_tx_progress(const uint32_t progress, const bool done);
+381 -193
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyleft () 2022 NotPike
* Copyright (C) 2023 Kyle Reed, zxkmm
*
* This file is part of PortaPack.
*
@@ -22,207 +23,342 @@
*/
#include "ui_playlist.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "baseband_api.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"
#include "oversample.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "utility.hpp"
#include <unistd.h>
#include <fstream>
using namespace portapack;
namespace fs = std::filesystem;
/* TODO
* - Should frequency overrides be saved in the playlist?
* - Start playing from current track (vs start over)?
*/
namespace ui {
void PlaylistView::set_ready() {
ready_signal = true;
}
// TODO: consolidate extesions into a shared header?
static const fs::path ppl_ext = u".PPL";
void PlaylistView::load_file(std::filesystem::path playlist_path) {
void PlaylistView::load_file(const fs::path& playlist_path) {
File playlist_file;
auto error = playlist_file.open(playlist_path.string());
if (!error.is_valid()) {
std::string line;
char one_char[1];
for (size_t pointer = 0; pointer < playlist_file.size(); pointer++) {
playlist_file.seek(pointer);
playlist_file.read(one_char, 1);
if ((int)one_char[0] >= ' ') {
line += one_char[0];
} else if (one_char[0] == '\n') {
txtline_process(line);
line.clear();
}
}
if (line.length() > 0) {
txtline_process(line);
}
if (error)
return;
auto reader = FileLineReader(playlist_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue; // Empty or comment line.
auto cols = split_string(line, ',');
auto entry = load_entry(trim(cols[0]));
if (!entry)
continue;
// Read optional delay value.
if (cols.size() > 1)
parse_int(cols[1], entry->ms_delay);
playlist_db_.emplace_back(*std::move(entry));
}
playlist_masterdb = playlist_db;
return;
}
void PlaylistView::txtline_process(std::string& line) {
playlist_entry new_item;
rf::Frequency f;
size_t previous = 0;
size_t current = line.find(',');
std::string freqs = line.substr(0, current);
previous = current + 1;
current = line.find(',', previous);
std::string file = line.substr(previous, current - previous);
previous = current + 1;
current = line.find(',', previous);
uint32_t sample_rate = strtoll(line.substr(previous).c_str(), nullptr, 10);
Optional<PlaylistView::playlist_entry> PlaylistView::load_entry(fs::path&& path) {
File capture_file;
f = strtoll(freqs.c_str(), nullptr, 0);
new_item.replay_frequency = f;
new_item.replay_file = "/" + file;
new_item.sample_rate = sample_rate;
new_item.next_delay = 0;
auto error = capture_file.open(path);
if (error)
return {};
playlist_db.push_back(std::move(new_item));
// Read metafile if it exists.
auto metadata_path = get_metadata_path(path);
auto metadata = read_metadata_file(metadata_path);
// If no metadata found, fallback to the TX frequency.
if (!metadata)
metadata = {transmitter_model.target_frequency(), 500'000};
return playlist_entry{
std::move(path),
*metadata,
capture_file.size(),
0u};
}
void PlaylistView::on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate) {
File data_file;
// Get file size
auto data_open_error = data_file.open("/" + new_file_path.string());
if (data_open_error.is_valid()) {
file_error();
void PlaylistView::on_file_changed(const fs::path& new_file_path) {
stop();
current_index_ = 0;
playlist_path_ = new_file_path;
playlist_db_.clear();
load_file(playlist_path_);
update_ui();
// TODO: fix in UI framework with 'try_focus()'?
// Hack around focus getting called by ctor before parent is set.
if (parent())
button_play.focus();
}
void PlaylistView::open_file(bool prompt_save) {
if (playlist_dirty_ && prompt_save) {
nav_.display_modal(
"Save?", "Save changes?", YESNO,
[this](bool choice) {
if (choice)
save_file(false);
});
nav_.set_on_pop([this]() { open_file(false); });
return;
}
file_path = new_file_path;
field_frequency.set_value(replay_frequency);
sample_rate = replay_sample_rate;
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 0) + "Hz");
auto file_size = data_file.size();
auto duration = (file_size * 1000) / (2 * 2 * sample_rate);
progressbar.set_max(file_size);
text_filename.set(file_path.filename().string().substr(0, 12));
text_duration.set(to_string_time_ms(duration));
button_play.focus();
auto open_view = nav_.push<FileLoadView>(".PPL");
open_view->push_dir(u"PLAYLIST");
open_view->on_changed = [this](fs::path new_file_path) {
on_file_changed(new_file_path);
};
}
void PlaylistView::on_tx_progress(const uint32_t progress) {
progressbar.set_value(progress);
void PlaylistView::save_file(bool show_dialogs) {
if (!playlist_dirty_ || playlist_path_.empty())
return;
ensure_directory(playlist_path_.parent_path());
File playlist_file;
auto error = playlist_file.create(playlist_path_.string());
if (error) {
if (show_dialogs)
nav_.display_modal(
"Save Error",
"Could not save file\n" + playlist_path_.string());
return;
}
for (const auto& entry : playlist_db_) {
playlist_file.write_line(
entry.path.string() + "," +
to_string_dec_uint(entry.ms_delay));
}
playlist_dirty_ = false;
if (show_dialogs)
nav_.display_modal(
"Save",
"Saved playlist\n" + playlist_path_.string());
}
void PlaylistView::focus() {
button_open.focus();
void PlaylistView::add_entry(fs::path&& path) {
if (playlist_path_.empty()) {
playlist_path_ = next_filename_matching_pattern(u"/PLAYLIST/PLAY_????.PPL");
// Hack around focus getting called by ctor before parent is set.
if (parent())
button_play.focus();
}
auto entry = load_entry(std::move(path));
if (entry) {
playlist_db_.emplace_back(*std::move(entry));
current_index_ = playlist_db_.size() - 1;
playlist_dirty_ = true;
}
update_ui();
}
void PlaylistView::file_error() {
nav_.display_modal("Error", "File " + file_path.string() + " read error. " + file_path.string());
void PlaylistView::delete_entry() {
if (playlist_db_.empty())
return;
// Ugh, the STL is gross.
playlist_db_.erase(playlist_db_.begin() + current_index_);
if (current_index_ > 0)
--current_index_;
playlist_dirty_ = true;
update_ui();
}
void PlaylistView::show_file_error(const fs::path& path, const std::string& message) {
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
}
PlaylistView::playlist_entry* PlaylistView::current() {
return playlist_db_.empty() ? nullptr : &playlist_db_[current_index_];
}
bool PlaylistView::is_active() const {
return (bool)replay_thread;
return replay_thread_ != nullptr;
}
bool PlaylistView::loop() const {
return (bool)playlist_db.size();
bool PlaylistView::at_end() const {
return current_index_ >= playlist_db_.size();
}
void PlaylistView::toggle() {
if (is_active()) {
stop(false);
} else {
if (is_active())
stop();
else
start();
}
}
void PlaylistView::start() {
stop(false);
if (playlist_db_.empty())
return; // Nothing to do.
playlist_entry item = playlist_db.front();
playlist_db.pop_front();
// playlist_entry item = playlist_db[0];
// for (playlist_entry item : playlist_db) {
// file_path = item.replay_file;
// rf::Frequency replay_frequency = strtoll(item.replay_frequency.c_str(),nullptr,10);
on_file_changed(item.replay_file, item.replay_frequency, item.sample_rate);
on_target_frequency_changed(item.replay_frequency);
std::unique_ptr<stream::Reader> reader;
auto p = std::make_unique<FileReader>();
auto open_error = p->open(file_path);
if (open_error.is_valid()) {
file_error();
return; // Fixes TX bug if there's a file error
} else {
reader = std::move(p);
}
if (reader) {
button_play.set_bitmap(&bitmap_stop);
baseband::set_sample_rate(sample_rate * 8);
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);
});
}
// Enable Bias Tee if selected
radio::set_antenna_bias(portapack::get_antenna_bias());
rf_amp = (transmitter_model.rf_amp()); // recover rf_amp settings applied from ui_transmiter.cpp
radio::enable({receiver_model.tuning_frequency(),
sample_rate * 8,
baseband_bandwidth,
rf::Direction::Transmit,
rf_amp, // previous code line : "receiver_model.rf_amp()," was passing the same rf_amp of all Receiver Apps
static_cast<int8_t>(receiver_model.lna()),
static_cast<int8_t>(receiver_model.vga())});
// }
current_index_ = 0u;
send_current_track();
}
void PlaylistView::stop(const bool do_loop) {
if (is_active()) {
replay_thread.reset();
}
if (do_loop) {
if (playlist_db.size() > 0) {
start();
} else {
playlist_db = playlist_masterdb;
start();
}
} else {
radio::set_antenna_bias(false); // Turn off Bias Tee
radio::disable();
button_play.set_bitmap(&bitmap_play);
/* Advance to the next track in the playlist. */
bool PlaylistView::next_track() {
++current_index_;
// Reset to the 0 once at the end to prevent current_index_
// from being outside the bounds of playlist_db_ when painting.
if (at_end()) {
current_index_ = 0;
return check_loop.value(); // Keep going if looping.
}
ready_signal = false;
return true;
}
void PlaylistView::handle_replay_thread_done(const uint32_t return_code) {
/* Transmits the current_entry_ */
void PlaylistView::send_current_track() {
// Prepare to send a file.
replay_thread_.reset();
transmitter_model.disable();
ready_signal_ = false;
if (!current())
return;
// TODO: Use a timer so the UI isn't frozen.
if (current()->ms_delay > 0)
chThdSleepMilliseconds(current()->ms_delay);
// Open the sample file to send.
auto reader = std::make_unique<FileConvertReader>();
auto error = reader->open(current()->path);
if (error) {
show_file_error(current()->path, "Can't open file to send.");
return;
}
// Update the sample rate in proc_replay baseband.
baseband::set_sample_rate(current()->metadata.sample_rate,
get_oversample_rate(current()->metadata.sample_rate));
// ReplayThread starts immediately on construction; must be set before creating.
transmitter_model.set_target_frequency(current()->metadata.center_frequency);
transmitter_model.set_sampling_rate(get_actual_sample_rate(current()->metadata.sample_rate));
transmitter_model.set_baseband_bandwidth(current()->metadata.sample_rate <= 500'000 ? 1'750'000 : 2'500'000); // TX LPF min 1M75 for SR <=500K, and 2M5 (by experimental test) for SR >500K
transmitter_model.enable();
// Reset the transmit progress bar.
progressbar_transmit.set_value(0);
// Use the ReplayThread class to send the data.
replay_thread_ = std::make_unique<ReplayThread>(
std::move(reader),
/* read_size */ 0x4000,
/* buffer_count */ 3,
&ready_signal_,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
// Now it's sending, update the UI.
update_ui();
}
void PlaylistView::stop() {
// This terminates the underlying chThread.
replay_thread_.reset();
transmitter_model.disable();
// Reset the transmit progress bar.
progressbar_transmit.set_value(0);
update_ui();
}
void PlaylistView::update_ui() {
if (playlist_db_.empty()) {
text_filename.set("-");
text_sample_rate.set("-");
text_duration.set("-");
if (playlist_path_.empty())
text_track.set("Open playlist or add capture.");
else
text_track.set(playlist_path_.filename().string());
progressbar_track.set_value(0);
progressbar_track.set_max(0);
progressbar_transmit.set_max(0);
} else {
chDbgAssert(!at_end(), "update_ui #1", "current_index_ invalid");
text_filename.set(current()->path.filename().string());
text_sample_rate.set(unit_auto_scale(current()->metadata.sample_rate, 3, (current()->metadata.sample_rate > 1000000) ? 2 : 0) + "Hz");
uint8_t sample_size = capture_file_sample_size(current()->path);
auto duration = ms_duration(current()->file_size, current()->metadata.sample_rate, sample_size);
text_duration.set(to_string_time_ms(duration));
field_frequency.set_value(current()->metadata.center_frequency);
text_track.set(
to_string_dec_uint(current_index_ + 1) + "/" +
to_string_dec_uint(playlist_db_.size()) + " " +
playlist_path_.filename().string());
progressbar_track.set_max(playlist_db_.size() - 1);
progressbar_track.set_value(current_index_);
progressbar_transmit.set_max(current()->file_size * sizeof(complex16_t) / sample_size);
}
button_play.set_bitmap(is_active() ? &bitmap_stop : &bitmap_play);
}
void PlaylistView::on_tx_progress(uint32_t progress) {
progressbar_transmit.set_value(progress);
}
void PlaylistView::handle_replay_thread_done(uint32_t return_code) {
if (return_code == ReplayThread::END_OF_FILE) {
stop(true);
} else if (return_code == ReplayThread::READ_ERROR) {
stop(false);
file_error();
if (next_track()) {
send_current_track();
return;
}
}
progressbar.set_value(0);
if (return_code == ReplayThread::READ_ERROR)
show_file_error(current()->path, "Replay read failed.");
stop();
}
PlaylistView::PlaylistView(
@@ -231,77 +367,129 @@ PlaylistView::PlaylistView(
baseband::run_image(portapack::spi_flash::image_tag_replay);
add_children({
&button_open,
&text_filename,
&text_sample_rate,
&text_duration,
&progressbar,
&progressbar_track,
&progressbar_transmit,
&field_frequency,
&tx_view, // this handles now the previous rfgain, rfamp
&tx_view,
&check_loop,
&button_play,
&text_track,
&button_prev,
&button_add,
&button_delete,
&button_open,
&button_save,
&button_next,
&waterfall,
});
field_frequency.set_value(target_frequency());
field_frequency.set_step(receiver_model.frequency_step());
ensure_directory(u"PLAYLIST");
waterfall.show_audio_spectrum_view(false);
field_frequency.set_value(100'000'000);
field_frequency.on_change = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
if (current())
current()->metadata.center_frequency = f;
};
field_frequency.on_edit = [this, &nav]() {
// TODO: Provide separate modal method/scheme?
auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->on_target_frequency_changed(f);
this->field_frequency.set_value(f);
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);
};
};
field_frequency.set_step(5000);
button_play.on_select = [this](ImageButton&) {
this->toggle();
toggle();
};
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
load_file(new_file_path);
button_add.on_select = [this, &nav]() {
if (is_active())
return;
auto open_view = nav_.push<FileLoadView>(".C*");
open_view->push_dir(u"CAPTURES");
open_view->on_changed = [this](fs::path path) {
add_entry(std::move(path));
};
};
button_delete.on_select = [this, &nav]() {
if (is_active())
return;
delete_entry();
};
button_open.on_select = [this, &nav]() {
if (is_active())
stop();
open_file();
};
button_save.on_select = [this]() {
if (is_active())
stop();
save_file();
};
button_prev.on_select = [this]() {
if (is_active())
return;
--current_index_;
if (at_end())
current_index_ = playlist_db_.size() - 1;
update_ui();
};
button_next.on_select = [this]() {
if (is_active())
return;
++current_index_;
if (at_end())
current_index_ = 0;
update_ui();
};
update_ui();
}
PlaylistView::PlaylistView(
NavigationView& nav,
const fs::path& path)
: PlaylistView(nav) {
auto ext = path.extension();
if (path_iequal(ext, ppl_ext))
on_file_changed(path);
else if (is_cxx_capture_file(path))
add_entry(fs::path{path});
}
PlaylistView::~PlaylistView() {
radio::disable();
transmitter_model.disable();
baseband::shutdown();
}
void PlaylistView::set_parent_rect(Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
ui::Rect waterfall_rect{
0, header_height, new_parent_rect.width(),
new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
void PlaylistView::focus() {
if (playlist_path_.empty())
button_add.focus();
else
button_play.focus();
}
void PlaylistView::on_hide() {
stop(false);
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
// it's being shown or hidden.
stop();
waterfall.on_hide();
View::on_hide();
}
void PlaylistView::set_parent_rect(const Rect new_parent_rect) {
View::set_parent_rect(new_parent_rect);
const ui::Rect waterfall_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height};
waterfall.set_parent_rect(waterfall_rect);
}
void PlaylistView::on_target_frequency_changed(rf::Frequency f) {
set_target_frequency(f);
}
void PlaylistView::set_target_frequency(const rf::Frequency new_value) {
persistent_memory::set_tuned_frequency(new_value);
;
}
rf::Frequency PlaylistView::target_frequency() const {
return persistent_memory::tuned_frequency();
}
} /* namespace ui */
+125 -69
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed, zxkmm
*
* This file is part of PortaPack.
*
@@ -20,135 +21,190 @@
* Boston, MA 02110-1301, USA.
*/
#define SHORT_UI true
#define NORMAL_UI false
#include "ui_widget.hpp"
#include "app_settings.hpp"
#include "bitmap.hpp"
#include "file.hpp"
#include "metadata_file.hpp"
#include "radio_state.hpp"
#include "replay_thread.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
#include "ui_spectrum.hpp"
#include "ui_transmitter.hpp"
#include "ui_widget.hpp"
#include <string>
#include <memory>
#include <deque>
#include "ui_transmitter.hpp"
#include <vector>
namespace ui {
class PlaylistView : public View {
public:
PlaylistView(NavigationView& nav);
PlaylistView(NavigationView& nav, const std::filesystem::path& path);
~PlaylistView();
void on_hide() override;
void set_parent_rect(const Rect new_parent_rect) override;
// Following 2 called by 'NavigationView::update_view' after view is created.
void set_parent_rect(Rect new_parent_rect) override;
void focus() override;
void on_hide() override;
std::string title() const override { return "Playlist"; };
std::string title() const override { return "Replay"; };
private:
NavigationView& nav_;
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_playlist", app_settings::Mode::TX};
static constexpr ui::Dim header_height = 3 * 16;
// More header == less spectrum view.
static constexpr ui::Dim header_height = 6 * 16;
struct playlist_entry {
rf::Frequency replay_frequency{0};
std::string replay_file{};
uint32_t sample_rate{};
uint32_t next_delay{};
std::filesystem::path path{};
capture_metadata metadata{};
File::Size file_size{};
uint32_t ms_delay{};
};
std::deque<playlist_entry> playlist_db{};
std::deque<playlist_entry> playlist_masterdb{};
uint32_t sample_rate = 0;
int32_t tx_gain{47};
bool rf_amp{true}; // aux private var to store temporal, Replay App rf_amp user selection.
static constexpr uint32_t baseband_bandwidth = 2500000;
const size_t read_size{16384};
const size_t buffer_count{3};
void load_file(std::filesystem::path playlist_path);
void txtline_process(std::string&);
void on_file_changed(std::filesystem::path new_file_path, rf::Frequency replay_frequency, uint32_t replay_sample_rate);
void on_target_frequency_changed(rf::Frequency f);
void on_tx_progress(const uint32_t progress);
void set_target_frequency(const rf::Frequency new_value);
rf::Frequency target_frequency() const;
std::unique_ptr<ReplayThread> replay_thread_{};
bool ready_signal_{}; // Used to signal the ReplayThread.
size_t current_index_{0};
bool playlist_dirty_{};
std::vector<playlist_entry> playlist_db_{};
std::filesystem::path playlist_path_{};
void load_file(const std::filesystem::path& path);
Optional<playlist_entry> load_entry(std::filesystem::path&& path);
void on_file_changed(const std::filesystem::path& path);
void open_file(bool prompt_save = true);
void save_file(bool show_dialogs = true);
void add_entry(std::filesystem::path&& path);
void delete_entry();
void reset();
void show_file_error(
const std::filesystem::path& path,
const std::string& message);
playlist_entry* current();
bool is_active() const;
bool at_end() const;
void toggle();
void start();
void stop(const bool do_loop);
bool is_active() const;
bool loop() const;
void set_ready();
void handle_replay_thread_done(const uint32_t return_code);
void file_error();
bool next_track();
void send_current_track();
void stop();
std::filesystem::path file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool ready_signal{false};
void update_ui();
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
"Open file"};
/* There are called by Message handlers. */
void on_tx_progress(uint32_t progress);
void handle_replay_thread_done(uint32_t return_code);
Text text_filename{
{11 * 8, 0 * 16, 12 * 8, 16},
"-"};
Text text_sample_rate{
{24 * 8, 0 * 16, 6 * 8, 16},
"-"};
Text text_duration{
{11 * 8, 1 * 16, 6 * 8, 16},
"-"};
ProgressBar progressbar{
{18 * 8, 1 * 16, 12 * 8, 16}};
{0 * 8, 0 * 16, 30 * 8, 16}};
FrequencyField field_frequency{
{0 * 8, 2 * 16},
};
{0 * 8, 1 * 16}};
Text text_sample_rate{
{10 * 8, 1 * 16, 7 * 8, 16}};
ProgressBar progressbar_track{
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
// (-1) to overlap with progressbar_track so there's
// only 1 pixel between them instead of 2.
ProgressBar progressbar_transmit{
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
Text text_duration{
{0 * 8, 2 * 16, 5 * 8, 16}};
// TODO: delay duration field.
TransmitterView2 tx_view{
// new handling of NumberField field_rfgain, NumberField field_rfamp
74, 1 * 8, SHORT_UI // x(columns), y (line) position. (Used in Repay / GPS Simul / Play list App)
// 10*8, 2*8, NORMAL_UI // x(columns), y (line) position. (Used in Mic App)
};
{11 * 8, 2 * 16},
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
4,
"Loop",
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Color::green(),
Color::black()};
spectrum::WaterfallWidget waterfall{};
Text text_track{
{0 * 8, 3 * 16, 30 * 8, 16}};
NewButton button_prev{
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_arrow_left,
Color::dark_grey()};
NewButton button_next{
{6 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_arrow_right,
Color::dark_grey()};
NewButton button_add{
{11 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_new_file,
Color::orange()};
NewButton button_delete{
{15 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_delete,
Color::orange()};
NewButton button_open{
{20 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_load,
Color::dark_blue()};
NewButton button_save{
{24 * 8, 4 * 16, 4 * 8, 2 * 16},
"",
&bitmap_icon_save,
Color::dark_blue()};
spectrum::WaterfallView waterfall{};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
this->handle_replay_thread_done(message.return_code);
[this](const Message* p) {
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
handle_replay_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* const p) {
const auto message = static_cast<const RequestSignalMessage*>(p);
[this](const Message* p) {
auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
this->set_ready();
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);
[this](const Message* p) {
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
on_tx_progress(message.progress);
}};
};
+8 -29
View File
@@ -23,7 +23,6 @@
#include "ui_pocsag_tx.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "string_format.hpp"
#include "ui_textentry.hpp"
@@ -39,13 +38,8 @@ void POCSAGTXView::focus() {
}
POCSAGTXView::~POCSAGTXView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_pocsag", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void POCSAGTXView::on_tx_progress(const uint32_t progress, const bool done) {
@@ -62,7 +56,7 @@ bool POCSAGTXView::start_tx() {
pocsag::BitRate bitrate;
std::vector<uint32_t> codewords;
address = field_address.value_dec_u32();
address = field_address.to_integer();
if (address > 0x1FFFFFU) {
nav_.display_modal("Bad address", "Address must be less\nthan 2097152.");
return false;
@@ -85,11 +79,10 @@ bool POCSAGTXView::start_tx() {
progressbar.set_max(total_frames);
transmitter_model.set_sampling_rate(2280000);
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.set_tx_gain(40);
// We left exactly same TX LPF settings as previous fw 1.7.4, TX LPF= 1M75 (min). It is fine even in max FM dev. 150khz and 2400 bauds.
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF . Pocsag is NBFM , using BW channel 25khz or 12khz
transmitter_model.enable();
uint8_t* data_ptr = shared_memory.bb_data.data;
@@ -143,24 +136,10 @@ POCSAGTXView::POCSAGTXView(
&progressbar,
&tx_view});
// load app settings
auto rc = settings.load("tx_pocsag", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
options_bitrate.set_selected_index(1); // 1200bps
options_type.set_selected_index(0); // Address only
// TODO: set_value for whole symfield
uint32_t reload_address = persistent_memory::pocsag_last_address();
for (uint32_t c = 0; c < 7; c++) {
field_address.set_sym(6 - c, reload_address % 10);
reload_address /= 10;
}
field_address.set_value(persistent_memory::pocsag_last_address());
options_type.on_change = [this](size_t, int32_t i) {
if (i == 2)
@@ -172,9 +151,9 @@ POCSAGTXView::POCSAGTXView(
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+10 -6
View File
@@ -32,6 +32,7 @@
#include "message.hpp"
#include "transmitter_model.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "pocsag.hpp"
using namespace pocsag;
@@ -58,6 +59,14 @@ class POCSAGTXView : public View {
std::string message{};
NavigationView& nav_;
TxRadioState radio_state_{
466175000 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_pocsag", app_settings::Mode::TX};
BCHCode BCH_code{
{1, 0, 1, 0, 0, 1},
5,
@@ -65,10 +74,6 @@ class POCSAGTXView : public View {
21,
2};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
void on_set_text(NavigationView& nav);
void on_tx_progress(const uint32_t progress, const bool done);
bool start_tx();
@@ -90,8 +95,7 @@ class POCSAGTXView : public View {
SymField field_address{
{11 * 8, 6 * 8},
7,
SymField::SYMFIELD_DEC};
7};
OptionsField options_type{
{11 * 8, 8 * 8},
+7 -26
View File
@@ -24,7 +24,6 @@
#include "portapack.hpp"
#include "baseband_api.hpp"
#include "cpld_update.hpp"
#include "portapack_shared_memory.hpp"
#include <cstring>
@@ -160,17 +159,12 @@ void RDSView::focus() {
}
RDSView::~RDSView() {
// save app settings
app_settings.tx_frequency = transmitter_model.tuning_frequency();
settings.save("tx_rds", &app_settings);
transmitter_model.disable();
hackrf::cpld::load_sram_no_verify(); // to leave all RX ok, without ghost signal problem at the exit.
baseband::shutdown(); // better this function at the end, not load_sram() that sometimes produces hang up.
baseband::shutdown();
}
void RDSView::start_tx() {
rds_flags.PI_code = sym_pi_code.value_hex_u64();
rds_flags.PI_code = sym_pi_code.to_integer();
rds_flags.PTY = options_pty.selected_index_value();
rds_flags.DI = view_PSN.mono_stereo ? 1 : 0;
rds_flags.TP = check_TP.value();
@@ -193,8 +187,6 @@ void RDSView::start_tx() {
else
frame_datetime.clear();
transmitter_model.set_sampling_rate(2280000U);
transmitter_model.set_baseband_bandwidth(1750000);
transmitter_model.enable();
tx_thread = std::make_unique<RDSThread>(frames);
@@ -218,30 +210,19 @@ RDSView::RDSView(
&tx_view,
});
// load app settings
auto rc = settings.load("tx_rds", &app_settings);
if (rc == SETTINGS_OK) {
transmitter_model.set_rf_amp(app_settings.tx_amp);
transmitter_model.set_channel_bandwidth(app_settings.channel_bandwidth);
transmitter_model.set_tuning_frequency(app_settings.tx_frequency);
transmitter_model.set_tx_gain(app_settings.tx_gain);
}
check_TP.set_value(true);
sym_pi_code.set_sym(0, 0xF);
sym_pi_code.set_sym(1, 0x3);
sym_pi_code.set_sym(2, 0xE);
sym_pi_code.set_sym(3, 0x0);
sym_pi_code.on_change = [this]() {
rds_flags.PI_code = sym_pi_code.value_hex_u64();
sym_pi_code.set_value(0xF3E0);
sym_pi_code.on_change = [this](SymField&) {
rds_flags.PI_code = sym_pi_code.to_integer();
};
options_pty.set_selected_index(0); // None
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
receiver_model.set_tuning_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+9 -4
View File
@@ -25,6 +25,7 @@
#include "ui_textentry.hpp"
#include "ui_tabview.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "rds.hpp"
using namespace rds;
@@ -140,9 +141,13 @@ class RDSView : public View {
NavigationView& nav_;
RDS_flags rds_flags{};
// app save settings
std::app_settings settings{};
std::app_settings::AppSettings app_settings{};
TxRadioState radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
2280000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_rds", app_settings::Mode::TX};
std::vector<RDSGroup> frame_psn{};
std::vector<RDSGroup> frame_radiotext{};
@@ -283,7 +288,7 @@ class RDSView : public View {
SymField sym_pi_code{
{13 * 8, 28 + 16},
4,
SymField::SYMFIELD_HEX};
SymField::Type::Hex};
/*OptionsField options_coverage {
{ 17 * 8, 32 + 8 },

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