Compare commits

..

240 Commits

Author SHA1 Message Date
jLynx ed834e3553 V2.0.0 setup (#1908)
* Update past_version.txt

* Update version.txt
2024-02-16 19:14:46 +00:00
Mark Thompson ce08d93ae2 Fix Pacman pause button glitches (again) (#1906) 2024-02-16 07:50:50 +01:00
Mark Thompson 13fd1b1f3b Support for configurable Menu Color & scrolling fix (#1905)
* Support for Configurable Menu Color Scheme
* Limit min value to 8 so doesn't get reset to default
* Increased max encoder rate multiplier value to 15
* Fixed menu scrolling issue
2024-02-16 07:19:43 +01:00
Mark Thompson d04c781ada Added Reboot app to Debug menu (#1904) 2024-02-16 07:18:33 +01:00
Mark Thompson 24605777a6 Show on_encoder delta in Buttons Test app (#1903) 2024-02-15 21:33:46 +01:00
Mark Thompson b5ac792853 Add color to position info lines in Notepad (#1902) 2024-02-15 18:12:40 +01:00
Mark Thompson 44c319dcc6 Refresh status bar after Pmem Reset (#1899) 2024-02-15 15:00:25 +01:00
Mark Thompson a442971b81 Eliminate unneeded screen flashing when scrolling menu (#1900) 2024-02-15 14:59:30 +01:00
Mark Thompson 8b5adb6bc1 Treat encoder rate multiplier value of 0 in pmem the same as 1 (#1898)
* Double-check encoder rate multiplier is not 0

* Eliminated redundant check in pmem init()
2024-02-15 07:05:05 +01:00
Bernd Herzog c3add0ce84 Cpld autodetect & boot splash screen (#1888)
* added delayed error message when hackrf cpld initialization fails

* refactoring

* implemented portapack cpld autodetection

* refactoring

* fixed valid config range

* added lcd fast setup

* added boot splash screen

* added one frame delay to remove flickering

* fixed config persistence
2024-02-14 23:17:33 +01:00
Mark Thompson 8725b01995 Fixed Pacman pause button (#1894) 2024-02-14 14:43:31 -06:00
E.T 936e8279f9 Add lto flag only to "internal" source files (#1895) 2024-02-14 13:54:20 -06:00
Mark Thompson 6193023c8f Fixed Pacman global object initialization (#1891) 2024-02-14 06:02:27 +01:00
Mark Thompson 1800939833 Fixed Tetris by eliminating some classes (#1890) 2024-02-13 20:54:40 +01:00
Mark Thompson 2f48fc2ef9 Disable LTO for external apps (#1886)
* Disable lto for external apps only

* Modified "shared external code" checks
2024-02-13 10:11:54 -06:00
Bernd Herzog 918ec0574f added delayed error message when hackrf cpld initialization fails (#1887)
* added delayed error message when hackrf cpld initialization fails

* refactoring
2024-02-13 13:06:47 +01:00
Mark Thompson 1139b22141 Fix compile error (#1884) 2024-02-12 19:04:48 +01:00
Brumi-2021 7598c25fc8 Solves random USB/LSB mode in SSB Mic App (H1R1). (#1882)
* Solves random USB/LSB mode in SSB Mic App (H1R1).

* format
2024-02-11 23:16:29 +01:00
Mark Thompson 0f6a9277a8 Warning for shared external code during build (#1881) 2024-02-11 21:25:00 +01:00
Mark Thompson 6a8650d484 Update Copyright in comment blocks (#1878) 2024-02-10 16:56:50 +01:00
Mark Thompson 46d9e02684 Encoder rotation rate multiplier support (#1876) 2024-02-10 09:32:03 +01:00
Mark Thompson 367479d163 Just shift menu icons up 1 row when hit bottom (#1874) 2024-02-10 09:21:35 +01:00
Mark Thompson bbbf5a37c8 Add progress bar to Soundboard app (#1875)
* Add progress bar to Soundboard app

* Clang
2024-02-10 09:04:00 +08:00
Mark Thompson b2ad1fa979 Cycle through brightness levels when icon selected (#1873) 2024-02-09 09:16:55 -06:00
Mark Thompson 998be5ba09 Set fake brightness to 50% default if never configured in Settings (#1871) 2024-02-09 18:36:02 +08:00
theHallwayThatBringMePassion 9e8d279298 fix double darken (#1866) 2024-02-08 18:39:57 +01:00
E.T fd03bb800c Brightness settings fixes 2.0 (#1870)
* Format

* Fix startup crash 🙈

* Revert "Revert "Brightness settings fixes" (#1865)"

Had to rebase then revert the revert to make git recognise and merge the change :)

This reverts commit b7e5bedf4b.

# Conflicts:
#	firmware/application/ui_navigation.cpp

* Add zxkmm to header too
2024-02-08 18:28:49 +01:00
Mark Thompson f80c28cde7 Rename playlist.ini to replay.ini to match app (#1864) 2024-02-08 11:34:40 +01:00
theHallwayThatBringMePassion b7e5bedf4b Revert "Brightness settings fixes" (#1865)
This reverts commit 994e426d1e.
2024-02-08 10:09:00 +08:00
E.T 994e426d1e Brightness settings fixes (#1863)
* Brightness settings fixes

- Statusbar toggle now updated when changing in settings page
- Annoying message boxes removed
- SystemView (aka whole screen) is now refreshed after toggling from status

* Format
2024-02-07 17:52:12 -06:00
Mark Thompson c5bf0d94f9 Skip over INFO chunk in WAV files (#1862) 2024-02-07 21:37:49 +01:00
Mark Thompson 8ad9ada015 Functionally revert PR #1837 (#1861) 2024-02-07 14:07:21 -06:00
Mark Thompson e854261124 WAV viewer tweaks (#1854) 2024-02-07 09:15:46 +01:00
theHallwayThatBringMePassion 0370b4eb55 fake brightness adjust (#1845)
* temp

* temp - 1

* next step note

* fix circuit dependency (to fix:can't read p.mem)

* fix circuit dependency (to fix:can't read p.mem)

* notes

* bitmap

* bitmap

* remove my stupid forward declearation

* clean up

* clean up

* level control

* clean up

* clean up

* clean up

* clean up

* format

* format

* format

* format

* format

* fix for comment advices

* fix for comment advices

* fix display issue in notepad app

* slightly increase performance...

* temp for grey_scale and high_constrast

* format

* note for cache implement

* fix for comment request

* fix for comment request
2024-02-07 16:07:30 +08:00
Matias Fernandez 5eef5b4e31 Rename Marine VHF freqman file to make more explicit what is the file about when you are browsing the files in the scanner app (#1858) 2024-02-07 00:08:37 -06:00
Mark Thompson 61d9c051de When setting Time & Date indicate if in DST range (#1851) 2024-02-06 14:25:44 -06:00
Mark Thompson 5ea1bff1e6 WAV Viewer & Soundboard enhancements (8 or 16-bit WAV files) (#1849)
* WAV Viewer & Soundboard enhancements
* Reduced width of sample rate field
2024-02-06 11:33:00 +01:00
Mark Thompson bc035cff6a ADSB retrieve_record just-in-case check (#1848) 2024-02-05 20:13:06 +01:00
E.T f2c008602d Adsb rx airline display fix (#1847)
* This doesn't do anything, just gives me some sanity :D

* Don’t abuse std

* Only look for airline info if the callsign is received

* Format…

* Let the airline update when callsign is received
2024-02-05 12:06:29 -06:00
Brumi-2021 b8073bca0f Adding_TX_IQ_phase_Calibration_to_Mic_App (#1843)
* Adding_TX_IQ_phase_Calibration_to_Mic_App

* Adding_persistent_CAL_data_and_correct_init_data
2024-02-03 17:57:45 -06:00
Mark Thompson c30a61441b Tetris external app (#1842)
* Tetris external app
* 4 levels
2024-02-03 19:33:36 +01:00
Matias Fernandez f0f279eec5 Add frequency ranges to looking glass presets (#1841)
* Add DECT frequency range. The most common allocated range fits within 1880MHz-1930MHz (different countries use different ranges). To provide extra detail at the edges I'm extending 1MHz the range on each direction. So the final range covers from 1879MHz to 1931MHz

* Add VHF Marine band. Even when Wikipedia says that the Marine Band covers from 156MHz to 174MHz, I couldn't find any channels defined above 163MHz. So, the final range I'm using goes from 155MHz to 163MHz to ensure some detail at the edges.

* Add a narrow range around 433MHz frequency. This is a widely used frequency for all sorts of remote controls, keyfobs, etc. I think it's useful to have a narrower range just focusing on this frequency

* Add FRS/GMRS frequency range

* Alphabetize the list of ranges, keeping the really big ranges of whole ITU bands and the full supported range at the end of the list
2024-02-03 09:57:07 +01:00
Mark Thompson 464cc8449e Use std:rand() in Touchscreen Test (#1840) 2024-02-03 09:54:10 +01:00
Mark Thompson ee2e57d702 2nd-Level Encoder Debouncing for scrappy encoder dials (#1838)
* Experimental encoder debouncing

* Experimental encoder debouncing

* Clang

* Comment changed
2024-02-01 14:13:28 -06:00
Mark Thompson a2a5fb166e Improved Debounce for Encoders (#1837)
* Fix variable type declaration

* Fix typo

* Two-bit encoder debouncing

* Slight optimization

* Comment change
2024-01-31 14:13:21 -06:00
Mark Thompson f59f5dfaa3 Fixed ms_duration function to show actual ms (#1836) 2024-01-31 21:08:17 +01:00
E.T 13f12227bd Remove ccache hack from dockerfiles (#1835)
Now we have a proper solution in cmake
2024-01-30 21:32:01 +01:00
Herman Semenov c9db21063b Speedup compilation using ccache package (#1832) 2024-01-30 20:55:59 +01:00
Mark Thompson 125184f300 Add audio playback ability to WAV Viewer app (#1829) 2024-01-30 20:55:32 +01:00
ʎʞsʌoʞᴉɐɥɔꓕ ɹʇoʎꓒ 8068517808 add warn when enable stealth mode & add nav valid check (#1823)
* add warn when enable stealth

* change FLASH ERROR to FLASH ERR cuz previous one were overlapped with date

* check if nav is valid
2024-01-30 13:58:40 +08:00
Mark Thompson a4f6bbda5e Fix compiler warning (#1827)
* Fix compiler warning

* Fix compiler warning

* Friggin C-lang
2024-01-29 18:07:32 -06:00
Totoo 8b2598fdac Call the flasher app when flashing from serial (#1825) 2024-01-29 20:52:35 +01:00
sommermoregentraum c36fe78bd5 pacman: cheat lifes and cheat skip level (#1821)
cheat lives and cheat skip level and fixed review issue. Thanks @NotherNgineer
2024-01-29 00:54:13 +08:00
Mark Thompson bd8385464e Tuner code readability (no functional change) (#1820) 2024-01-28 10:02:11 -06:00
Totoo 8c996b5bc6 Embedded friendly usb (#1817) 2024-01-27 19:33:08 +01:00
Mark Thompson d1901939e1 Fix tuning of 2170-2300 and 2700-2740 MHz (#1819) 2024-01-27 13:30:32 +01:00
Mark Thompson 41ec11fb66 Check app checksums during untar (#1815) 2024-01-27 13:29:24 +01:00
Mark Thompson df47490d85 Allow 3 boot failures before Config Mode starts (#1816)
* Allow 3 boot attempts before Config Mode starts

* Add files via upload
2024-01-26 21:02:52 +13:00
Totoo a2f6d14b15 ISS for aprs, for the request #1810 (#1814) 2024-01-25 21:10:43 +01:00
Mark Thompson 6a6c6d6502 Add Checksums to Firmware & External App images (#1809)
* Pad image to 1MB and add simple checksum

* Test code to verify firmware checksum

* Comment out unneeded zlib

* Add files via upload

* Print space remaining in ROM

* Append checksum to external apps too

* Check external app checksums when loading

* Is it 2024 already?!

* Validate firmware checksum before flashing

* Add files via upload

* Added flash error warning to nav screen

* Clang

* Replaced some hard-coded values with #defines

* Check FW checksum before USB serial flash too

* Add files via upload
2024-01-24 16:37:21 -06:00
Mark Thompson 2d98c5d311 Resolve Soundboard tone key issue (#1806)
* Resolve Soundboard tone key issue
2024-01-23 20:53:04 +01:00
sommermoregentraum 03b13f8ab0 prevent crash when nothing to broadcast in soundbrd app (#1807)
* prevent crash when nothing to broadcast in soundbrd app

* format
2024-01-22 22:15:37 -06:00
E.T 6e74ad942f Gh workflow changelog formatting fix (#1803)
* Remove replacement of newlines with hex codes

Assuming that its unnecessary as we are no longer outputting these via stdout but pipe it into some binary that could handle newlines in its stdin therefore don't need and support this transformation

* Attempt 2 after reading gh docs :D

* Fix changelog for stable release too
2024-01-22 10:50:14 +01:00
Mark Thompson 0f85f247b6 Fix rounding bug when displaying frequencies (#1801) 2024-01-22 07:54:29 +01:00
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
404 changed files with 29896 additions and 3453 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.
+2 -2
View File
@@ -6,8 +6,8 @@ from datetime import datetime, timedelta, timezone
# Set up your personal access token and the repository details
token = os.environ.get('GH_TOKEN')
repo_owner = "eried"
repo_name = "portapack-mayhem"
repo_owner = "portapack-mayhem"
repo_name = "mayhem-firmware"
def print_stable_changelog(previous_sha):
+28 -13
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: |
@@ -49,10 +54,10 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
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
@@ -61,16 +66,16 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && 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.GH_TOKEN }}
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<<EOF'
python3 .github/workflows/changelog.py
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
- name: Create Release
id: create_release
@@ -88,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
+33 -17
View File
@@ -9,22 +9,27 @@ 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
@@ -33,10 +38,10 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
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
@@ -45,16 +50,16 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && 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.GH_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
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<<EOF'
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
- name: Create Release
id: create_release
@@ -66,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
+1 -1
View File
@@ -1 +1 @@
v1.7.4
v1.9.1
+1 -1
View File
@@ -1 +1 @@
v1.8.0
v2.0.0
+5 -3
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
@@ -67,8 +68,9 @@ CMakeFiles/
.DS_Store
/firmware/CMakeCache.txt
# Python env
# Python env/ venv
env/
venv/
# Other
*.bak
-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 3.0)
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,6 +42,10 @@ 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(
@@ -48,6 +54,8 @@ execute_process(
)
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)
@@ -64,5 +72,12 @@ 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})
find_program(CCACHE "ccache")
if(CCACHE)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
endif(CCACHE)
enable_testing()
add_subdirectory(firmware)
+24 -15
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)*
# What is this?
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
[![What is?](https://img.youtube.com/vi/alrFbY5vxt4/0.jpg)](https://www.youtube.com/watch?v=alrFbY5vxt4)
[![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,31 +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).
## 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).
+16 -15
View File
@@ -10,32 +10,33 @@ 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
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD cd .. && cd build && \
cmake .. && make firmware
# replace make with ninja temporarily while building your image if you prefer to use that by default
CMD ["make"]
+1 -5
View File
@@ -19,9 +19,5 @@ RUN apk add --no-cache g++ gcc clang clang-static clang-dev llvm-dev llvm-static
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
# Configure CCACHE
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
+16 -15
View File
@@ -10,32 +10,33 @@ 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
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD cd .. && cd build && \
cmake .. && make firmware
# 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>
+51 -1
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)
@@ -33,6 +35,18 @@ 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)
@@ -48,12 +62,22 @@ 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 ${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 -i -R -w ${FIRMWARE_FILENAME}
@@ -67,6 +91,21 @@ add_custom_target(
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.
add_custom_command(
OUTPUT ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
@@ -86,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
)
+48 -19
View File
@@ -44,7 +44,7 @@ if(cpp20_supported)
else()
set(USE_CPPOPT "-std=c++17")
endif()
set(USE_CPPOPT "${USE_CPPOPT} -flto -fno-rtti -fno-exceptions -Weffc++ -Wuninitialized -Wno-volatile")
set(USE_CPPOPT "${USE_CPPOPT} -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)
@@ -52,7 +52,7 @@ set(USE_LINK_GC yes)
# Linker extra options here.
set(USE_LDOPT)
# Enable this if you want link time optimizations (LTO)
# Enable this if you want link time optimizations (LTO) - this flag affects chibios only
set(USE_LTO no)
# If enabled, this option allows to compile the application in THUMB mode.
@@ -113,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
@@ -162,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
@@ -190,6 +202,12 @@ set(CPPSRC
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
@@ -243,12 +261,14 @@ set(CPPSRC
apps/acars_app.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/analog_tv_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/lge_app.cpp
# apps/gps_sim_app.cpp
# apps/lge_app.cpp
apps/pocsag_app.cpp
# apps/replay_app.cpp
apps/soundboard_app.cpp
@@ -261,29 +281,30 @@ set(CPPSRC
apps/ui_aprs_tx.cpp
apps/ui_bht_tx.cpp
apps/ui_btle_rx.cpp
apps/ui_coasterp.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_freqman.cpp
apps/ui_fsk_rx.cpp
apps/ui_iq_trim.cpp
apps/ui_jammer.cpp
# apps/ui_jammer.cpp
# apps/ui_keyfob.cpp
apps/ui_lcr.cpp
apps/ui_level.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_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_recon.cpp
apps/ui_remote.cpp
apps/ui_scanner.cpp
apps/ui_sd_over_usb.cpp
@@ -292,24 +313,26 @@ set(CPPSRC
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_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
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
@@ -323,10 +346,13 @@ set(CPPSRC
${CPLD_20170522_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
${EXTCPPSRC}
view_factory_base.cpp
)
set_source_files_properties(${CPPSRC} PROPERTIES COMPILE_FLAGS -flto) # Add lto flag to the non-external sources only
list (APPEND CPPSRC ${EXTCPPSRC}) # Append external sources after setting lto flag to internal ones
# C sources to be compiled in ARM mode regardless of the global setting.
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
# option that results in lower performance and larger code size.
@@ -357,6 +383,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
@@ -400,7 +429,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}\"' -DVERSION_MD5=${VERSION_MD5}")
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)
+3 -2
View File
@@ -40,7 +40,7 @@ using namespace portapack;
namespace {
fs::path get_settings_path(const std::string& app_name) {
return fs::path{u"/SETTINGS"} / app_name + u".ini";
return fs::path{SETTINGS_DIR} / app_name + u".ini";
}
} // namespace
@@ -156,6 +156,7 @@ 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;
@@ -249,7 +250,7 @@ SettingsManager::SettingsManager(
settings_.options = options;
// Pre-alloc enough for app settings and additional settings.
additional_settings.reserve(17 + additional_settings.size());
additional_settings.reserve(COMMON_APP_SETTINGS_COUNT + additional_settings.size());
bindings_ = std::move(additional_settings);
// Settings should always be loaded because apps now rely
+7 -1
View File
@@ -36,9 +36,14 @@
#include "max283x.hpp"
#include "string_format.hpp"
// Folder to store app settings, pmem_fileflag, and date_fileflag
#define SETTINGS_DIR u"/SETTINGS"
// 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 {
@@ -108,6 +113,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
namespace app_settings {
enum class Mode : uint8_t {
NO_RF = 0x00,
RX = 0x01,
TX = 0x02,
RX_TX = 0x03, // Both TX/RX
@@ -140,8 +146,8 @@ struct AppSettings {
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. */
+4 -3
View File
@@ -64,9 +64,9 @@ static std::string mmsi(
static std::string mid(
const ais::MMSI& mmsi) {
std::database db;
database db;
std::string mid_code = "";
std::database::MidDBRecord mid_record = {};
database::MidDBRecord mid_record = {};
int return_code = 0;
// Try getting the country name from mids.db using MID code for given MMSI
@@ -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() {
+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__*/
+26 -11
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -54,20 +55,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 +89,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 +101,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,6 +123,8 @@ ERTAppView::ERTAppView(NavigationView&) {
&recent_entries_view,
});
field_frequency.set_step(1000000);
receiver_model.enable();
logger = std::make_unique<ERTLogger>();
@@ -134,7 +149,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()) {
+15 -6
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -24,6 +25,7 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "ui_rssi.hpp"
#include "ui_channel.hpp"
@@ -71,11 +73,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},
@@ -95,7 +98,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{};
@@ -126,6 +129,7 @@ class ERTAppView : public View {
ERTRecentEntries recent{};
std::unique_ptr<ERTLogger> logger{};
NavigationView& nav_;
RxRadioState radio_state_{
911600000 /* frequency */,
2500000 /* bandwidth */,
@@ -135,14 +139,19 @@ class ERTAppView : public View {
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}};
+36 -14
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -26,6 +27,7 @@
#include "string_format.hpp"
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "audio.hpp"
using namespace tonekey;
using namespace portapack;
@@ -40,6 +42,7 @@ void SoundBoardView::stop() {
if (is_active())
replay_thread.reset();
audio::output::stop();
transmitter_model.disable();
tx_view.set_transmitting(false);
@@ -49,7 +52,7 @@ void SoundBoardView::stop() {
void SoundBoardView::handle_replay_thread_done(const uint32_t return_code) {
stop();
// progressbar.set_value(0);
progressbar.set_value(0);
if (return_code == ReplayThread::END_OF_FILE) {
if (check_random.value()) {
@@ -78,9 +81,16 @@ void SoundBoardView::file_error() {
}
void SoundBoardView::start_tx(const uint32_t id) {
if (file_list.empty()) {
file_error();
return;
}
auto reader = std::make_unique<WAVFileReader>();
uint32_t tone_key_index = options_tone_key.selected_index();
uint32_t sample_rate;
uint8_t bits_per_sample;
stop();
@@ -91,11 +101,12 @@ void SoundBoardView::start_tx(const uint32_t id) {
playing_id = id;
// progressbar.set_max(reader->sample_count());
progressbar.set_max(reader->sample_count());
// button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
@@ -111,18 +122,22 @@ void SoundBoardView::start_tx(const uint32_t id) {
1536000 / 20, // Update vu-meter at 20Hz
transmitter_model.channel_bandwidth(),
0, // Gain is unused
8, // shift_bits_s16, default 8 bits, but also unused
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
0, // AM
0, // DSB
0, // USB
0 // LSB
8, // shift_bits_s16, default 8 bits, but also unused
bits_per_sample,
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
transmitter_model.enable();
tx_view.set_transmitting(true);
if (tone_key_index == 0)
audio::output::start();
}
/*void SoundBoardView::show_infos() {
@@ -134,8 +149,7 @@ void SoundBoardView::start_tx(const uint32_t id) {
}*/
void SoundBoardView::on_tx_progress(const uint32_t progress) {
(void)progress; // avoid warning
// progressbar.set_value(progress);
progressbar.set_value(progress);
}
void SoundBoardView::on_select_entry() {
@@ -160,7 +174,7 @@ void SoundBoardView::refresh_list() {
if (entry_extension == ".WAV") {
if (reader->open(u"/WAV/" + entry.path().native())) {
if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) {
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
// sounds[c].ms_duration = reader->ms_duration();
// sounds[c].path = u"WAV/" + entry.path().native();
if (count >= (page - 1) * 100 && count < page * 100) {
@@ -199,7 +213,7 @@ void SoundBoardView::refresh_list() {
for (size_t n = 0; n < file_list.size(); n++) {
menu_view.add_item({file_list[n].string().substr(0, 30),
ui::Color::white(),
ui::Color::dark_magenta(),
nullptr,
[this](KeyEvent) {
on_select_entry();
@@ -226,7 +240,9 @@ SoundBoardView::SoundBoardView(
&options_tone_key,
//&text_title,
//&text_duration,
//&progressbar,
&progressbar,
&field_volume,
&text_volume_disabled,
&page_info,
&check_loop,
&check_random,
@@ -252,6 +268,13 @@ SoundBoardView::SoundBoardView(
tone_keys_populate(options_tone_key);
options_tone_key.set_selected_index(0);
text_volume_disabled.hidden(true);
options_tone_key.on_change = [this](size_t index, OptionsField::value_t) {
bool tone_key_enabled = (index != 0);
text_volume_disabled.hidden(!tone_key_enabled);
field_volume.hidden(tone_key_enabled);
};
check_loop.set_value(false);
check_random.set_value(false);
@@ -274,7 +297,6 @@ SoundBoardView::SoundBoardView(
SoundBoardView::~SoundBoardView() {
stop();
transmitter_model.disable();
baseband::shutdown();
}
+12 -6
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -91,7 +92,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{
@@ -103,7 +104,7 @@ class SoundBoardView : public View {
"<="};
Text page_info{
{0, 30 * 8 - 4, 30 * 8, 16}};
{0, 29 * 8, 30 * 8, 16}};
MenuView menu_view{
{0, 0, 240, 175},
@@ -122,10 +123,16 @@ class SoundBoardView : public View {
};*/
OptionsField options_tone_key{
{32, 180},
{4 * 8, 180},
18,
{}};
AudioVolumeField field_volume{
{28 * 8, 180}};
Text text_volume_disabled{
{28 * 8, 180, 3 * 8, 16},
"--"};
Checkbox check_loop{
{0, 25 * 8 + 4},
4,
@@ -136,9 +143,8 @@ class SoundBoardView : public View {
6,
"Random"};
// ProgressBar progressbar {
// { 0 * 8, 30 * 8 - 4, 30 * 8, 16 }
// };
ProgressBar progressbar{
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
TransmitterView tx_view{
16 * 16,
+7 -11
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -34,9 +35,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 +44,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) {
@@ -165,18 +163,16 @@ TPMSAppView::TPMSAppView(NavigationView&) {
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) {
+22 -3
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -37,6 +38,17 @@
#include "tpms_packet.hpp"
namespace tpms {
namespace format {
static bool units_psi{false};
static bool units_fahr{false};
} /* namespace format */
} /* namespace tpms */
namespace std {
} /* namespace std */
@@ -103,11 +115,17 @@ class TPMSAppView : public View {
private:
RxRadioState radio_state_{
315000000 /* frequency*/,
314950000 /* frequency*/,
1750000 /* bandwidth */,
2457600 /* sampling rate */};
app_settings::SettingsManager settings_{
"rx_tpms", app_settings::Mode::RX};
"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,
@@ -129,11 +147,12 @@ 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},
{"315", 314950000},
{"434", 433920000},
}};
@@ -19,7 +19,7 @@ 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
// 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");
@@ -37,11 +37,12 @@ void AboutView::update() {
break;
case 2:
console.writeln("NotherNgineer,zxkmm,u-foka");
console.writeln("Netro,HTotoo");
console.writeln("");
break;
case 3:
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
// 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");
@@ -54,7 +55,7 @@ void AboutView::update() {
break;
case 4:
// https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
// 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("");
+380 -321
View File
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -20,21 +22,23 @@
* 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 {
bool ac_details_view_active{false};
static std::string get_map_tag(const AircraftRecentEntry& entry) {
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
}
template <>
void RecentEntriesTable<AircraftRecentEntries>::draw(
@@ -43,23 +47,25 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
Painter& painter,
const Style& style) {
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) {
entry_string = "";
target_color = Color::green();
} else if (entry_age < ADSB_RECENT) {
entry_string = STR_COLOR_LIGHT_GREY;
target_color = Color::light_grey();
} else {
entry_string = STR_COLOR_DARK_GREY;
target_color = Color::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();
};
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 +78,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);
database db{};
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 +138,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 +161,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 +177,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,115 +199,201 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView(
text_type.set("Powered parachute/paraplane");
}
break;
case DATABASE_RECORD_NOT_FOUND:
// Defaults should be filled by the constructor
break;
case DATABASE_NOT_FOUND:
text_manufacturer.set("No icao24.db file");
break;
}
button_close.on_select = [&nav](Button&) {
ac_details_view_active = false;
nav.pop();
};
};
}
// End of Aicraft details
void ADSBRxAircraftDetailsView::focus() {
button_close.focus();
}
/* ADSBRxDetailsView *************************************/
ADSBRxDetailsView::ADSBRxDetailsView(
NavigationView& nav,
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});
text_icao_address.set(entry_.icao_str);
button_aircraft_details.on_select = [this, &nav](Button&) {
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&) {
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 ADSBRxDetailsView::focus() {
button_see_map.focus();
}
void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
entry_copy = entry;
uint32_t age = entry_copy.age;
entry_ = entry;
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 {
// 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() {
// The following won't change for a given airborne aircraft.
// Try getting the airline's name from airlines.db.
if (!airline_checked && !entry_.callsign.empty()) {
airline_checked = true;
database db;
database::AirlinesDBRecord airline_record;
std::string airline_code = entry_.callsign.substr(0, 3);
auto return_code = db.retrieve_airline_record(&airline_record, airline_code);
switch (return_code) {
case DATABASE_RECORD_FOUND:
text_airline.set(airline_record.airline);
text_country.set(airline_record.country);
break;
case DATABASE_RECORD_NOT_FOUND:
// text_airline.set("-"); // It's what it is constructed with
// text_country.set("-"); // It's what it is constructed with
break;
case DATABASE_NOT_FOUND:
text_airline.set("No airlines.db file");
break;
}
}
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_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_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_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);
}
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));
}
ADSBRxDetailsView::~ADSBRxDetailsView() {
ac_details_view_active = false;
on_close_();
}
/* ADSBRxView ********************************************/
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});
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,
&status_frame,
&status_good_frame});
std::unique_ptr<ADSBLogger> logger{};
update(entry_copy);
recent_entries_view.set_parent_rect({0, 16, 240, 272});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detail_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(entry);
// 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);
text_country.set(airline_record.country);
break;
case DATABASE_NOT_FOUND:
text_airline.set("No airlines.db file");
break;
}
text_callsign.set(entry_copy.callsign);
text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6));
button_aircraft_details.on_select = [this, &nav](Button&) {
ac_details_view_active = true;
aircraft_details_view = nav.push<ADSBRxAircraftDetailsView>(entry_copy, [this]() { send_updates = false; });
send_updates = false;
nav.set_on_pop([this]() {
detail_key = AircraftRecentEntry::invalid_key;
details_view = nullptr;
});
};
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;
}
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
};
void ADSBRxView::focus() {
field_vga.focus();
logger = std::make_unique<ADSBLogger>();
logger->append(LOG_ROOT_DIR "/ADSB.TXT");
receiver_model.enable();
baseband::set_adsb();
}
ADSBRxView::~ADSBRxView() {
@@ -296,232 +402,185 @@ ADSBRxView::~ADSBRxView() {
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::focus() {
field_vga.focus();
}
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();
status_frame.toggle();
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
// 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;
}
entry.inc_hit();
if (logger) {
logentry += to_string_hex_array(frame.get_raw_data(), 14) + " ";
logentry += "ICAO:" + entry.icaoStr + " ";
}
// 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 (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();
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);
// 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 + " ";
}
entry.set_info_string(std::move(str_info));
}
// 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);
} 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() {
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();
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::updateDetailsAndMap(int ageStep) {
ui::GeoMarker marker;
bool storeNewMarkers = false;
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.
// NB: Temporarily pausing updates in rtc_timer_tick context when viewing AC Details screen (kludge for some Guru faults)
// TODO: More targeted blocking of updates in rtc_timer_tick when ADSB processes are running
if (ac_details_view_active)
return;
if (details_view) {
// The details view is showing, forward updates to that UI.
bool current_updated = false;
bool map_needs_update = false;
// Sort and truncate the entries, grouped, newest group first
sort_entries_by_state();
truncate_entries(recent);
remove_old_entries();
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;
}
// 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;
// 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 {
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
// Note -- Storing the selected entry too, in case map panning occurs
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) {
// Main page is the top view. Redraw the entries view.
recent_entries_view.set_dirty();
}
}
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});
void ADSBRxView::update_recent_entries(int age_delta) {
for (auto& entry : recent)
entry.inc_age(age_delta);
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;
};
// Sort and truncate the entries, grouped by state, newest first.
sort_entries_by_state();
truncate_entries(recent);
remove_expired_entries();
}
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
AircraftRecentEntry& ADSBRxView::find_or_create_entry(uint32_t ICAO_address) {
// Find ...
auto it = find(recent, ICAO_address);
if (it != recent.end())
return *it;
baseband::set_adsb();
// ... or Create.
return recent.emplace_front(ICAO_address);
}
receiver_model.enable();
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 */
+148 -103
View File
@@ -1,6 +1,8 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -21,29 +23,23 @@
*/
#include "ui.hpp"
#include "ui_receiver.hpp"
#include "ui_geomap.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 "radio_state.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 +62,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 +90,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 +136,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 +146,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 +251,37 @@ 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};
bool airline_checked{false};
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Color::light_grey()},
@@ -319,8 +334,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,46 +361,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:
RxRadioState radio_state_{
1090000000 /* frequency */,
2500000 /* bandwidth */,
2000000 /* sampling rate */,
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};
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()}};
@@ -386,7 +421,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{
+3 -1
View File
@@ -85,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);
});
+2 -1
View File
@@ -58,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},
+5 -2
View File
@@ -101,6 +101,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
field_frequency.set_value(145175000);
} else if (i == 3) {
field_frequency.set_value(144575000);
} else if (i == 4) {
field_frequency.set_value(145825000);
}
};
@@ -258,7 +260,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();
@@ -317,7 +319,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() {
@@ -339,6 +341,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,*/
+2 -1
View File
@@ -221,7 +221,8 @@ class APRSRxView : public View {
{{"NA ", 0},
{"EUR", 1},
{"AUS", 2},
{"NZ ", 3}}};
{"NZ ", 3},
{"ISS", 4}}};
RxFrequencyField field_frequency{
{3 * 8, 0 * 16},
+1 -1
View File
@@ -70,7 +70,7 @@ BTLERxView::BTLERxView(NavigationView& nav)
};
// 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();
+2
View File
@@ -60,6 +60,8 @@ class BTLERxView : public View {
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{
+153 -66
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -124,8 +125,8 @@ 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 {
@@ -159,11 +160,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() {
@@ -179,12 +177,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(),
@@ -196,15 +197,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(),
@@ -212,19 +214,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&) {
@@ -233,16 +277,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() {
@@ -257,6 +323,11 @@ bool ControlsSwitchesWidget::on_key(const KeyEvent key) {
return true;
}
bool ControlsSwitchesWidget::on_encoder(const EncoderEvent delta) {
last_delta = delta;
return true;
}
void ControlsSwitchesWidget::paint(Painter& painter) {
const auto pos = screen_pos();
@@ -339,6 +410,8 @@ void ControlsSwitchesWidget::paint(Painter& painter) {
switches_event >>= 1;
}
painter.draw_string({5 * 8, 12 * 16}, Styles::light_grey, to_string_dec_int(last_delta, 3));
}
void ControlsSwitchesWidget::on_frame_sync() {
@@ -376,22 +449,25 @@ 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
}
/* DebugReboot **********************************************/
DebugReboot::DebugReboot(NavigationView& nav) {
(void)nav;
LPC_RGU->RESET_CTRL[0] = (1 << 0);
while (1)
__WFE();
}
/* DebugMenuView *********************************************************/
DebugMenuView::DebugMenuView(NavigationView& nav) {
@@ -402,10 +478,12 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
{"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", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugMemoryView>(); }},
{"P.Memory", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<DebugPmemView>(); }},
{"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>(); } },
{"Reboot", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<DebugReboot>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SDCardDebugView>(); }},
{"Temperature", ui::Color::dark_cyan(), &bitmap_icon_temperature, [&nav]() { nav.push<TemperatureView>(); }},
{"Touch Test", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<DebugScreenTest>(); }},
@@ -418,30 +496,53 @@ DebugMenuView::DebugMenuView(NavigationView& nav) {
set_max_rows(2); // allow wider buttons
}
/* DebugPmemView *********************************************************/
/* DebugMemoryDumpView *********************************************************/
uint32_t pmem_checksum(volatile const uint32_t data[63]) {
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
for (size_t i = 0; i < 63; i++) {
const auto word = data[i];
crc.process_byte((word >> 0) & 0xff);
crc.process_byte((word >> 8) & 0xff);
crc.process_byte((word >> 16) & 0xff);
crc.process_byte((word >> 24) & 0xff);
}
return crc.checksum();
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();
};
}
DebugPmemView::DebugPmemView(NavigationView& nav)
: data{*reinterpret_cast<pmem_data*>(memory::map::backup_ram.base())}, registers_widget(RegistersWidgetConfig{page_size, 8}, std::bind(&DebugPmemView::registers_widget_feed, this, std::placeholders::_1)) {
static_assert(sizeof(pmem_data) == memory::map::backup_ram.size());
void DebugMemoryDumpView::focus() {
button_done.focus();
}
add_children({&text_page, &registers_widget, &text_checksum, &text_checksum2, &button_ok});
/* 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(data.check_value, 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(pmem_checksum(data.regfile), 8));
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();
@@ -451,7 +552,7 @@ DebugPmemView::DebugPmemView(NavigationView& nav)
}
bool DebugPmemView::on_encoder(const EncoderEvent delta) {
page = std::max(0l, std::min((int32_t)page_max, page + delta));
registers_widget.set_page(std::max(0ul, std::min((uint32_t)page_count - 1, registers_widget.page() + delta)));
update();
@@ -463,22 +564,15 @@ void DebugPmemView::focus() {
}
void DebugPmemView::update() {
text_page.set(to_string_hex(page_size * page, 2) + "+");
registers_widget.update();
}
uint32_t DebugPmemView::registers_widget_feed(const size_t register_number) {
if (page_size * page + register_number >= memory::map::backup_ram.size()) {
return 0xff;
}
return data.regfile[(page_size * page + register_number) / 4] >> (register_number % 4 * 8);
}
/* DebugScreenTest ****************************************************/
DebugScreenTest::DebugScreenTest(NavigationView& nav)
: nav_{nav} {
set_focusable(true);
std::srand(LPC_RTC->CTIME0);
}
bool DebugScreenTest::on_key(const KeyEvent key) {
@@ -488,10 +582,10 @@ bool DebugScreenTest::on_key(const KeyEvent key) {
nav_.pop();
break;
case KeyEvent::Down:
painter.fill_rectangle({0, 0, screen_width, screen_height}, semirand());
painter.fill_rectangle({0, 0, screen_width, screen_height}, std::rand());
break;
case KeyEvent::Left:
pen_color = semirand();
pen_color = std::rand();
break;
default:
break;
@@ -511,17 +605,10 @@ bool DebugScreenTest::on_touch(const TouchEvent event) {
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();
pen_color = std::rand();
}
/* DebugLCRView *******************************************************/
+110 -32
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -48,8 +49,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{
@@ -131,9 +132,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;
@@ -174,17 +185,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;
@@ -194,13 +210,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{};
@@ -208,12 +220,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 {
@@ -222,18 +252,21 @@ class ControlsSwitchesWidget : public Widget {
Rect parent_rect)
: Widget{parent_rect},
key_event_mask(0),
long_press_key_event_mask{0} {
long_press_key_event_mask{0},
last_delta{0} {
set_focusable(true);
}
void on_show() override;
bool on_key(const KeyEvent key) override;
bool on_encoder(const EncoderEvent delta) override;
void paint(Painter& painter) override;
private:
uint8_t key_event_mask;
uint8_t long_press_key_event_mask;
EncoderEvent last_delta;
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
@@ -255,6 +288,7 @@ class DebugControlsView : public View {
private:
Labels labels{
{{8 * 8, 1 * 16}, "Controls State", Color::white()},
{{0 * 8, 11 * 16}, "Dial:", Color::grey()},
{{0 * 8, 14 * 16}, "Long-Press Mode:", Color::grey()}};
ControlsSwitchesWidget switches_widget{
@@ -274,6 +308,58 @@ class DebugControlsView : public View {
"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);
@@ -282,19 +368,8 @@ class DebugPmemView : public View {
std::string title() const override { return "P.Mem Debug"; }
private:
struct pmem_data {
uint32_t regfile[63];
uint32_t check_value;
};
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_max{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size - 1};
int32_t page{0};
volatile const pmem_data& data;
Text text_page{{16, 16, 208, 16}};
static constexpr uint8_t page_count{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size};
RegistersWidget registers_widget;
@@ -307,7 +382,6 @@ class DebugPmemView : public View {
};
void update();
uint32_t registers_widget_feed(const size_t register_number);
};
class DebugScreenTest : public View {
@@ -316,7 +390,6 @@ class DebugScreenTest : public View {
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:
@@ -351,6 +424,11 @@ class DebugPeripheralsMenuView : public BtnGridView {
std::string title() const override { return "Peripherals"; };
};
class DebugReboot : public BtnGridView {
public:
DebugReboot(NavigationView& nav);
};
class DebugMenuView : public BtnGridView {
public:
DebugMenuView(NavigationView& nav);
+13 -10
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Bernd Herzog
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -48,7 +49,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));
@@ -94,23 +95,25 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
&text_info_line_7,
&text_info_line_8,
&text_info_line_9,
&text_info_line_10});
&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)portapack::receiver_model.modulation(), 10));
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
text_info_line_8.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
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 = 10 + 2;
constexpr auto lines = 11 + 2;
painter.fill_rectangle(
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
+11 -7
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Bernd Herzog
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -83,13 +84,15 @@ class DfuMenu2 : public View {
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()}};
{{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}, ""};
@@ -101,6 +104,7 @@ class DfuMenu2 : public View {
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 */
+8 -1
View File
@@ -156,7 +156,7 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
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())) {
@@ -568,6 +568,7 @@ FileManagerView::FileManagerView(
&button_open_notepad,
&button_rename_timestamp,
&button_open_iq_trim,
&button_show_hidden_files,
});
menu_view.on_highlight = [this]() {
@@ -658,6 +659,12 @@ FileManagerView::FileManagerView(
} 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
+8
View File
@@ -104,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()}};
@@ -278,6 +280,12 @@ class FileManagerView : public FileManBaseView {
{},
&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 */
+74 -2
View File
@@ -21,6 +21,7 @@
*/
#include "ui_flash_utility.hpp"
#include "ui_styles.hpp"
#include "portapack_shared_memory.hpp"
namespace ui {
@@ -30,6 +31,30 @@ static const char16_t* firmware_folder = u"/FIRMWARE";
Thread* FlashUtilityView::thread{nullptr};
static constexpr size_t max_filename_length = 26;
bool valid_firmware_file(std::filesystem::path::string_type path) {
File firmware_file;
uint32_t read_buffer[128];
uint32_t checksum{(uint32_t)~FLASH_EXPECTED_CHECKSUM}; // initializing to invalid checksum in case file can't be read
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
auto result = firmware_file.open(path.c_str());
if (!result.is_valid()) {
checksum = 0;
for (uint32_t i = 0; i < FLASH_ROM_SIZE / sizeof(read_buffer); i++) {
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
// if file is smaller than 1MB, assume it's a downgrade to an old FW version and ignore the checksum
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
checksum = FLASH_EXPECTED_CHECKSUM;
break;
}
checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer));
}
}
return (checksum == FLASH_EXPECTED_CHECKSUM);
}
FlashUtilityView::FlashUtilityView(NavigationView& nav)
: nav_(nav) {
add_children({&labels,
@@ -50,6 +75,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) {
@@ -65,8 +101,43 @@ void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path
});
}
void FlashUtilityView::flash_firmware(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);
});
return res;
}
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
ui::Painter painter;
if (endsWith(path, u".tar")) {
// extract, then update
path = extract_tar(u'/' + path, painter).native();
}
if (path.empty() || !valid_firmware_file(path.c_str())) {
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
painter.draw_string({0, 60}, Styles::red, "BAD FIRMWARE FILE");
chThdSleepMilliseconds(5000);
return false;
}
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
ui::Color::black());
@@ -74,11 +145,12 @@ 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);
m0_halt();
return true; // fixes compiler warning (line should not be reached due to halt)
}
void FlashUtilityView::focus() {
+11 -2
View File
@@ -29,11 +29,17 @@
#include "ff.h"
#include "baseband_api.hpp"
#include "core_control.hpp"
#include "untar.hpp"
#include <cstdint>
#define FLASH_ROM_SIZE 1048576
#define FLASH_STARTING_ADDRESS 0x00000000
#define FLASH_EXPECTED_CHECKSUM 0x00000000
namespace ui {
bool valid_firmware_file(std::filesystem::path::string_type path);
class FlashUtilityView : public View {
public:
FlashUtilityView(NavigationView& nav);
@@ -41,6 +47,7 @@ class FlashUtilityView : public View {
void focus() override;
std::string title() const override { return "Flash Utility"; };
bool flash_firmware(std::filesystem::path::string_type path);
private:
NavigationView& nav_;
@@ -55,8 +62,10 @@ class FlashUtilityView : public View {
{0, 2 * 8, 240, 26 * 8},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
void firmware_selected(std::filesystem::path::string_type path);
void flash_firmware(std::filesystem::path::string_type path);
bool endsWith(const std::u16string& str, const std::u16string& suffix);
};
} /* namespace ui */
+2 -1
View File
@@ -412,7 +412,8 @@ void FrequencyEditView::refresh_ui() {
auto is_range = entry_.type == freqman_type::Range;
auto is_ham = entry_.type == freqman_type::HamRadio;
auto has_freq_b = is_range || is_ham;
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);
+2 -1
View File
@@ -220,7 +220,8 @@ class FrequencyEditView : public View {
{"Single", 0},
{"Range", 1},
{"HamRadio", 2},
{"Raw", 3},
{"Repeater", 3},
{"Raw", 4},
}};
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
+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__*/
+20 -2
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -40,6 +41,7 @@ IQTrimView::IQTrimView(NavigationView& nav)
&text_samples,
&text_max,
&field_cutoff,
&field_amplify,
&button_trim,
});
@@ -72,6 +74,11 @@ IQTrimView::IQTrimView(NavigationView& nav)
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();
@@ -133,7 +140,18 @@ void IQTrimView::focus() {
void IQTrimView::refresh_ui() {
field_path.set_text(path_.filename().string());
text_samples.set(to_string_dec_uint(info_->sample_count));
text_max.set(to_string_dec_uint(info_->max_power));
// 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();
}
@@ -191,7 +209,7 @@ bool IQTrimView::trim_capture() {
}
progress_ui.show_trimming();
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback());
trimmed = iq::trim_capture_with_range(path_, trim_range, progress_ui.get_callback(), field_amplify.value());
progress_ui.clear();
if (!trimmed)
+11 -1
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -107,6 +108,8 @@ class IQTrimView : public View {
{{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{
@@ -135,7 +138,7 @@ class IQTrimView : public View {
"0"};
Text text_max{
{9 * 8, 9 * 16, 10 * 8, 1 * 16},
{9 * 8, 9 * 16, 20 * 8, 1 * 16},
"0"};
NumberField field_cutoff{
@@ -145,6 +148,13 @@ class IQTrimView : public View {
1,
' '};
NumberField field_amplify{
{9 * 8, 12 * 16},
1,
{1, 9},
1,
' '};
Button button_trim{
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
"Trim"};
+11 -18
View File
@@ -142,31 +142,24 @@ LevelView::LevelView(NavigationView& nav)
}
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 */
+377 -380
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -31,6 +32,7 @@
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "wm8731.hpp"
#include "radio.hpp"
#include <cstring>
@@ -51,7 +53,7 @@ void MicTXView::focus() {
field_rxfrequency.focus();
break;
default:
field_va.focus();
check_va.focus();
break;
}
}
@@ -60,24 +62,57 @@ 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
8, // bits per sample
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) {
@@ -101,12 +136,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) {
@@ -128,7 +175,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;
}
@@ -138,42 +186,39 @@ 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);
}
}
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, 0k).
} 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;
}
if (bool_same_F_tx_rx_enabled) // when stop TX, define to which freq RX we return
receiver_model.set_target_frequency(tx_frequency); // Update freq also for RX = TX
else
receiver_model.set_target_frequency(rx_frequency); // Now with separate 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();
audio::output::start();
} else { // These incredibly convoluted steps are required for the vumeter to reappear when stopping RX.
@@ -183,14 +228,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::set_ptt_visibility(bool v) {
tx_button.hidden(!v);
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_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(
@@ -199,400 +312,276 @@ 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,
hackrf_r9 ? &field_tx_iq_phase_cal_2839 : &field_tx_iq_phase_cal_2837,
&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);
options_gain.on_change = [this](size_t, int32_t v) {
mic_gain = v / 10.0;
configure_baseband();
check_rogerbeep.set_value(rogerbeep_enabled);
check_rogerbeep.on_select = [this](Checkbox&, bool v) {
rogerbeep_enabled = v;
};
options_gain.set_selected_index(1); // x1.0 preselected default.
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_off
configure_baseband(); // to update in real time, sending msg, var-parameters >>shift_bits FM msg, to audio_tx from M0 to M4 Proc -
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);
};
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
if (hackrf_r9) { // MAX2839 has 6 bits IQ CAL phasse adjustment.
field_tx_iq_phase_cal_2839.set_value(iq_phase_calibration_value);
field_tx_iq_phase_cal_2839.on_change = [this](int32_t v) {
iq_phase_calibration_value = v;
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
};
options_wm8731_boost_mode.set_selected_index(3); // preset GUI index 3 as default WM -> -02 dB's.
} 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.
} else { // MAX2837 has 5 bits IQ CAL phase adjustment.
field_tx_iq_phase_cal_2837.set_value(iq_phase_calibration_value);
field_tx_iq_phase_cal_2837.on_change = [this](int32_t v) {
iq_phase_calibration_value = v;
radio::set_tx_max283x_iq_phase_calibration(iq_phase_calibration_value);
};
}
// options_ak4951_alc_mode.set_selected_index(0);
options_gain.on_change = [this](size_t, int32_t v) {
mic_gain_x10 = v;
configure_baseband();
};
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) {
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 -
};
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 {
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, 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);
}
// 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.set_value(transmitter_model.target_frequency());
field_frequency.set_step(receiver_model.frequency_step());
field_frequency.on_change = [this](rf::Frequency f) {
tx_frequency = f;
if (!rx_enabled) { // not activated receiver. just update freq TX
if (!rx_enabled)
transmitter_model.set_target_frequency(f);
} else { // activated receiver.
if (bool_same_F_tx_rx_enabled) // user selected common freq- TX = RX
receiver_model.set_target_frequency(f); // Update common freq also for RX
}
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_target_frequency(f);
} else {
if (bool_same_F_tx_rx_enabled)
receiver_model.set_target_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.
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(2); // // bw 16k (2) default
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.
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(0); // bw 200k (0) default
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("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.
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("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;
}
// 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_target_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_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.target_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_target_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_target_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.
@@ -602,7 +591,12 @@ MicTXView::MicTXView(
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(
@@ -611,16 +605,15 @@ MicTXView::MicTXView(
: MicTXView(nav) {
// Try to use the modulation/bandwidth from RX settings.
// TODO: These concepts should be merged so there's only one.
// TODO: enums/constants for these indexes.
switch (override.mode) {
case ReceiverModel::Mode::AMAudio:
options_mode.set_selected_index(2);
options_mode.set_selected_index(MIC_MOD_AM);
break;
case ReceiverModel::Mode::NarrowbandFMAudio:
options_mode.set_selected_index(0);
options_mode.set_selected_index(MIC_MOD_NFM);
break;
case ReceiverModel::Mode::WidebandFMAudio:
options_mode.set_selected_index(1);
options_mode.set_selected_index(MIC_MOD_WFM);
break;
// Unsupported modulations.
@@ -630,6 +623,8 @@ MicTXView::MicTXView(
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.
@@ -637,10 +632,12 @@ MicTXView::MicTXView(
MicTXView::~MicTXView() {
audio::input::stop();
transmitter_model.set_target_frequency(tx_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);
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();
}
+98 -72
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -70,67 +71,87 @@ 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_target_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 set_ptt_visibility(bool v);
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
sampling_rate /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_mic", app_settings::Mode::RX_TX,
app_settings::Options::UseGlobalTargetFrequency};
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};
uint8_t iq_phase_calibration_value{15};
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},
{"iq_phase_calibration"sv, &iq_phase_calibration_value},
}};
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
// TODO: Some of this stuff is mutually exclusive. Need a better representation.
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() },
{{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()},
@@ -142,26 +163,12 @@ class MicTXView : public View {
{{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()}};
{{19 * 8, (27 * 8) + 2}, "AMP:", Color::light_grey()},
{{21 * 8, (31 * 8)}, "TX-IQ-CAL:", 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()},
{{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()}};
{{17 * 8, 1 * 8}, "ALC", Color::light_grey()}};
VuMeter vumeter{
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
@@ -224,28 +231,19 @@ class MicTXView : public View {
{24 * 8, 5 * 8},
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},
4,
{{" 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},
@@ -277,6 +275,12 @@ class MicTXView : public View {
"Roger beep",
false};
Checkbox check_mic_to_HP{
{18 * 8, (14 * 8) + 4},
10,
"Hear Mic",
false};
Checkbox check_rxactive{
{3 * 8, (21 * 8) - 7},
8, // it was 18, but if it is string size should be 8
@@ -286,7 +290,7 @@ class MicTXView : public View {
Checkbox check_common_freq_tx_rx{
{18 * 8, (21 * 8) - 7},
8,
"F TX=RX",
"F RX=TX",
false};
AudioVolumeField field_volume{
@@ -338,11 +342,33 @@ class MicTXView : public View {
' ',
};
NumberField field_tx_iq_phase_cal_2837{
{24 * 8, (33 * 8)},
2,
{0, 31}, // 5 bits IQ CAL phase adjustment.
1,
' ',
};
NumberField field_tx_iq_phase_cal_2839{
{24 * 8, (33 * 8)},
2,
{0, 63}, // 6 bits IQ CAL phasse adjustment.
1,
' ',
};
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) {
+43 -23
View File
@@ -56,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);
@@ -121,10 +131,10 @@ bool MorseView::start_tx() {
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;
@@ -133,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;
@@ -175,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();
@@ -188,7 +198,7 @@ MorseView::MorseView(
add_children({&labels,
&checkbox_foxhunt,
&options_foxhunt,
&field_foxhunt,
&field_speed,
&field_tone,
&options_modulation,
@@ -200,36 +210,46 @@ MorseView::MorseView(
&tx_view});
// 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);
};
+25 -24
View File
@@ -65,7 +65,6 @@ class MorseView : public View {
private:
NavigationView& nav_;
std::string buffer{"PORTAPACK"};
std::string message{};
uint32_t time_units{0};
@@ -74,14 +73,25 @@ class MorseView : public View {
1750000 /* bandwidth */,
1536000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_morse", app_settings::Mode::TX};
enum modulation_t {
CW = 0,
FM = 1
};
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();
@@ -90,7 +100,6 @@ class MorseView : public View {
Thread* ookthread{nullptr};
Thread* loopthread{nullptr};
bool foxhunt_mode{false};
bool run{false};
Labels labels{
@@ -104,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},
@@ -136,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},
+2 -11
View File
@@ -277,18 +277,9 @@ NumbersStationView::NumbersStationView(
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();
+2 -3
View File
@@ -78,8 +78,6 @@ void NuoptixView::transmit(bool setup) {
}
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.enable();
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
@@ -136,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,7 +155,7 @@ NuoptixView::NuoptixView(
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.target_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+2 -1
View File
@@ -3,6 +3,7 @@
* Copyright (C) 2016 Furrtek
* Copyleft () 2022 NotPike
* Copyright (C) 2023 Kyle Reed, zxkmm
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -389,7 +390,7 @@ PlaylistView::PlaylistView(
ensure_directory(u"PLAYLIST");
waterfall.show_audio_spectrum_view(false);
field_frequency.set_value(100'000'000);
field_frequency.set_value(transmitter_model.target_frequency());
field_frequency.on_change = [this](rf::Frequency f) {
if (current())
current()->metadata.center_frequency = f;
+2 -1
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Kyle Reed, zxkmm
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -56,7 +57,7 @@ class PlaylistView : public View {
NavigationView& nav_;
TxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"tx_playlist", app_settings::Mode::TX};
"tx_replay", app_settings::Mode::TX};
// More header == less spectrum view.
static constexpr ui::Dim header_height = 6 * 16;
+285 -58
View File
@@ -22,13 +22,24 @@
*/
#include "ui_recon.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "capture_app.hpp"
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.hpp"
#include "tone_key.hpp"
#include "replay_app.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"
#include "oversample.hpp"
#include "baseband_api.hpp"
#include "metadata_file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "utility.hpp"
#include "replay_thread.hpp"
using namespace portapack;
using namespace tonekey;
@@ -37,6 +48,30 @@ namespace fs = std::filesystem;
namespace ui {
void ReconView::reload_restart_recon() {
frequency_file_load();
if (frequency_list.size() > 0) {
if (fwd) {
button_dir.set_text("FW>");
} else {
button_dir.set_text("<RW");
}
recon_resume();
}
if (scanner_mode) {
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
} else {
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
}
void ReconView::check_update_ranges_from_current() {
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
if (update_ranges && !manual_mode) {
@@ -67,7 +102,7 @@ void ReconView::set_loop_config(bool v) {
persistent_memory::set_recon_continuous(continuous);
}
void ReconView::recon_stop_recording() {
void ReconView::recon_stop_recording(bool exiting) {
if (is_recording) {
if (field_mode.selected_index_value() == SPEC_MODULATION)
button_audio_app.set_text("RAW");
@@ -75,13 +110,17 @@ void ReconView::recon_stop_recording() {
button_audio_app.set_text("AUDIO");
button_audio_app.set_style(&Styles::white);
record_view->stop();
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
button_config.set_style(&Styles::white);
is_recording = false;
// repeater mode
if (!exiting && persistent_memory::recon_repeat_recorded()) {
start_repeat();
}
}
}
void ReconView::clear_freqlist_for_ui_action() {
recon_stop_recording();
recon_stop_recording(false);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
// flag to detect and reload frequency_list
@@ -111,6 +150,9 @@ void ReconView::update_description() {
case freqman_type::HamRadio:
description = "H: ";
break;
case freqman_type::Repeater:
description = "L: ";
break;
default:
description = "S: ";
}
@@ -182,6 +224,7 @@ void ReconView::load_persisted_settings() {
load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
load_repeaters = persistent_memory::recon_load_repeaters();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
}
@@ -265,9 +308,16 @@ void ReconView::focus() {
}
ReconView::~ReconView() {
recon_stop_recording();
if (recon_tx) {
replay_thread.reset();
}
recon_stop_recording(true);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
transmitter_model.disable();
receiver_model.disable();
baseband::shutdown();
}
@@ -275,9 +325,20 @@ ReconView::~ReconView() {
ReconView::ReconView(NavigationView& nav)
: nav_{nav} {
chrono_start = chTimeNow();
tx_view.hidden(true);
// set record View
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO",
u"AUTO_AUDIO", u"AUDIO",
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
record_view->set_auto_trim(false);
record_view->hidden(true);
record_view->on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
add_children({&labels,
&field_lna,
&field_vga,
@@ -314,13 +375,9 @@ ReconView::ReconView(NavigationView& nav)
&button_restart,
&button_mic_app,
&button_remove,
record_view.get()});
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
record_view.get(),
&progressbar,
&tx_view});
def_step = 0;
load_persisted_settings();
@@ -481,10 +538,9 @@ ReconView::ReconView(NavigationView& nav)
button_remove.on_select = [this](ButtonWithEncoder&) {
handle_remove_current_item();
// TODO: refresh UI.
timer = 0;
freq_lock = 0;
recon_redraw();
};
button_remove.on_change = [this]() {
@@ -493,7 +549,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_manual_recon.on_select = [this](Button&) {
button_remove.set_text("DELETE");
button_remove.set_text("<DELETE>");
button_add.hidden(false);
scanner_mode = false;
manual_mode = true;
@@ -567,27 +623,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_restart.on_select = [this](Button&) {
frequency_file_load(true);
if (frequency_list.size() > 0) {
if (fwd) {
button_dir.set_text("FW>");
} else {
button_dir.set_text("<RW");
}
recon_resume();
}
if (scanner_mode) {
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
} else {
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
reload_restart_recon();
};
button_add.on_select = [this](ButtonWithEncoder&) {
@@ -607,14 +643,14 @@ ReconView::ReconView(NavigationView& nav)
scanner_mode = false;
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
button_remove.set_text("REMOVE");
button_remove.set_text("<REMOVE>");
} else {
scanner_mode = true;
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
button_remove.set_text("DELETE");
button_remove.set_text("<DELETE>");
}
frequency_file_load(true);
frequency_file_load();
if (autostart) {
recon_resume();
} else {
@@ -642,7 +678,7 @@ ReconView::ReconView(NavigationView& nav)
load_persisted_settings();
ui_settings.save();
frequency_file_load(false);
frequency_file_load();
freqlist_cleared_for_ui_action = false;
if (autostart) {
@@ -705,11 +741,14 @@ ReconView::ReconView(NavigationView& nav)
change_mode(AM_MODULATION); // start on AM.
field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector
// tx progress bar
progressbar.hidden(true);
if (filedelete) {
delete_file(freq_file_path);
}
frequency_file_load(false); /* do not stop all at start */
frequency_file_load(); /* do not stop all at start */
if (autostart) {
recon_resume();
} else {
@@ -718,7 +757,7 @@ ReconView::ReconView(NavigationView& nav)
recon_redraw();
}
void ReconView::frequency_file_load(bool) {
void ReconView::frequency_file_load() {
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
@@ -742,7 +781,8 @@ void ReconView::frequency_file_load(bool) {
freqman_load_options options{
.load_freqs = load_freqs,
.load_ranges = load_ranges,
.load_hamradios = load_hamradios};
.load_hamradios = load_hamradios,
.load_repeaters = load_repeaters};
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
file_name.set_style(&Styles::red);
desc_cycle.set("...empty file...");
@@ -777,6 +817,12 @@ void ReconView::frequency_file_load(bool) {
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (recon_tx)
return;
if (is_repeat_active())
return;
chrono_end = chTimeNow();
systime_t time_interval = chrono_end - chrono_start;
chrono_start = chrono_end;
@@ -784,7 +830,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// hack to reload the list if it was cleared by going into CONFIG
if (freqlist_cleared_for_ui_action) {
if (!manual_mode) {
frequency_file_load(false);
frequency_file_load();
}
if (autostart && !user_pause) {
recon_resume();
@@ -805,7 +851,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (status != 1) {
status = 1;
if (wait != 0) {
recon_stop_recording();
recon_stop_recording(false);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
}
@@ -836,9 +882,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// contents of a possible recon_start_recording(), but not yet since it's only called once
if (auto_record_locked && !is_recording) {
button_audio_app.set_style(&Styles::red);
if (field_mode.selected_index_value() == SPEC_MODULATION)
if (field_mode.selected_index_value() == SPEC_MODULATION) {
button_audio_app.set_text("RAW REC");
else
} else
button_audio_app.set_text("WAV REC");
record_view->start();
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
@@ -957,6 +1003,26 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
}
}
}
} else if (current_entry().type == freqman_type::Repeater) {
// repeater is like single, we only listen on frequency_a and then jump to next entry
if ((fwd && stepper == 0) || stepper > 0) { // forward
current_index++;
entry_has_changed = true;
// looping
if ((uint32_t)current_index >= frequency_list.size()) {
has_looped = true;
current_index = 0;
}
} else if ((!fwd && stepper == 0) || stepper < 0) {
// reverse
current_index--;
entry_has_changed = true;
// if previous if under the list => go back from end
if (current_index < 0) {
has_looped = true;
current_index = frequency_list.size() - 1;
}
}
}
// set index to boundary if !continuous
if (has_looped && !continuous) {
@@ -985,6 +1051,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (entry_has_changed) {
timer = 0;
switch (current_entry().type) {
case freqman_type::Repeater:
case freqman_type::Single:
freq = current_entry().frequency_a;
break;
@@ -1018,7 +1085,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (stepper > 0) stepper--;
} // if( recon || stepper != 0 || index_stepper != 0 )
} // if (frequency_list.size() > 0 )
} /* on_statistic_updates */
} /* on_statistics_updates */
}
handle_retune();
recon_redraw();
@@ -1081,28 +1148,39 @@ void ReconView::on_stepper_delta(int32_t v) {
}
size_t ReconView::change_mode(freqman_index_t new_mod) {
if (recon_tx || is_repeat_active())
return 0;
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
recon_stop_recording();
remove_child(record_view.get());
record_view.reset();
if (new_mod == SPEC_MODULATION) {
recon_stop_recording(false);
if (record_view != nullptr) {
remove_child(record_view.get());
record_view.reset();
}
if (persistent_memory::recon_repeat_recorded()) {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"RECON_REPEAT.C16", u"CAPTURES",
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_as_is(true);
} else if (new_mod == SPEC_MODULATION) {
audio::output::stop();
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_RAW_", u"CAPTURES",
u"AUTO_RAW", u"CAPTURES",
RecordView::FileType::RawS16, 16384, 3);
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO",
u"AUTO_AUDIO", u"AUDIO",
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
}
record_view->set_auto_trim(false);
add_child(record_view.get());
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [this](std::string message) {
nav_.display_modal("Error", message);
};
receiver_model.disable();
transmitter_model.disable();
baseband::shutdown();
size_t recording_sampling_rate = 0;
switch (new_mod) {
@@ -1145,7 +1223,6 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
// record_view determines the correct oversampling to apply and returns the actual sample rate.
auto actual_sampling_rate = record_view->set_sampling_rate(sampling_rate);
// The radio needs to know the effective sampling rate.
receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
@@ -1214,12 +1291,162 @@ void ReconView::handle_remove_current_item() {
if (frequency_list.size() > 0) {
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
entry = current_entry();
freq = entry.frequency_a;
} else {
current_index = 0;
text_cycle.set_text(" ");
}
update_description();
}
void ReconView::on_repeat_tx_progress(const uint32_t progress) {
progressbar.set_value(progress);
}
void ReconView::repeat_file_error(const std::filesystem::path& path, const std::string& message) {
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
}
bool ReconView::is_repeat_active() const {
return replay_thread != nullptr;
}
void ReconView::start_repeat() {
// Prepare to send a file.
std::filesystem::path rawfile = u"/" + repeat_rec_path + u"/" + repeat_rec_file;
std::filesystem::path rawmeta = u"/" + repeat_rec_path + u"/" + repeat_rec_meta;
if (recon_tx == false) {
recon_tx = true;
if (record_view != nullptr) {
record_view->stop();
remove_child(record_view.get());
record_view.reset();
}
receiver_model.disable();
transmitter_model.disable();
baseband::shutdown();
baseband::run_image(portapack::spi_flash::image_tag_replay);
size_t rawsize = 0;
{
File capture_file;
auto error = capture_file.open(rawfile);
if (error) {
repeat_file_error(rawfile, "Can't open file to send for size");
return;
}
rawsize = capture_file.size();
}
// Reset the transmit progress bar.
uint8_t sample_size = std::filesystem::capture_file_sample_size(rawfile);
progressbar.set_value(0);
progressbar.set_max(rawsize * sizeof(complex16_t) / sample_size);
progressbar.hidden(false);
auto metadata = read_metadata_file(rawmeta);
// If no metadata found, fallback to the TX frequency.
if (!metadata) {
metadata = {freq, 500'000};
repeat_file_error(rawmeta, "Can't open file to read meta, using default rx_freq,500'000");
}
// Update the sample rate in proc_replay baseband.
baseband::set_sample_rate(metadata->sample_rate,
get_oversample_rate(metadata->sample_rate));
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
transmitter_model.set_baseband_bandwidth(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
// set TX to repeater TX freq if entry is Repeater and have a valid freq, else use recorded one
if (current_entry().type == freqman_type::Repeater && current_entry().frequency_b != 0) {
transmitter_model.set_target_frequency(current_entry().frequency_b);
} else {
transmitter_model.set_target_frequency(metadata->center_frequency);
}
// set TX powers and enable transmitter
transmitter_model.set_tx_gain(persistent_memory::recon_repeat_gain());
transmitter_model.set_rf_amp(persistent_memory::recon_repeat_amp());
transmitter_model.enable();
}
// clear replay thread and set reader
replay_thread.reset();
auto reader = std::make_unique<FileConvertReader>();
auto error = reader->open(rawfile);
if (error) {
repeat_file_error(rawfile, "Can't open file to send to thread");
return;
}
// wait for TX if needed (hackish, direct screen update since the UI will be blocked)
if (persistent_memory::recon_repeat_delay() > 0) {
uint8_t delay = persistent_memory::recon_repeat_delay();
Painter p;
while (delay > 0) {
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
// update display information
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Color::light_grey());
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, Styles::red, delay_message);
// sleep 1 second
chThdSleepMilliseconds(1000);
// decre delay
if (delay > 0)
delay = delay - 1;
else
break;
}
}
// ReplayThread starts immediately on construction; must be set before creating.
repeat_ready_signal = true;
repeat_cur_rep++;
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
/* read_size */ repeat_read_size,
/* buffer_count */ repeat_buffer_count,
&repeat_ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
}
void ReconView::stop_repeat(const bool do_loop) {
repeat_ready_signal = false;
if (is_repeat_active()) {
replay_thread.reset();
transmitter_model.disable();
}
// repeat transmit if current number of repetitions (repeat_cur_rep) is < recon configured number of repetitions (recon_repeat_nb)
if (do_loop && repeat_cur_rep < persistent_memory::recon_repeat_nb()) {
start_repeat();
} else {
repeat_cur_rep = 0;
recon_tx = false;
reload_restart_recon();
progressbar.hidden(true);
set_dirty(); // fix progressbar no hiding
}
}
void ReconView::handle_repeat_thread_done(const uint32_t return_code) {
if (return_code == ReplayThread::END_OF_FILE) {
stop_repeat(true);
} else if (return_code == ReplayThread::READ_ERROR) {
stop_repeat(false);
repeat_file_error(u"/" + repeat_rec_path + u"/" + repeat_rec_file, "Can't open file to send.");
}
}
} /* namespace ui */
+75 -10
View File
@@ -41,11 +41,19 @@
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_recon_settings.hpp"
#include "ui_transmitter.hpp"
#include "replay_thread.hpp"
#include "metadata_file.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_freq_field.hpp"
#include "ui_spectrum.hpp"
#include <string>
#include <memory>
namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
enum class recon_mode : uint8_t {
Recon,
Scanner,
@@ -66,10 +74,17 @@ class ReconView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
500000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_recon"sv, app_settings::Mode::RX};
"rx_tx_recon"sv, app_settings::Mode::RX_TX};
;
void reload_restart_recon();
void check_update_ranges_from_current();
void set_loop_config(bool v);
void clear_freqlist_for_ui_action();
@@ -81,7 +96,7 @@ class ReconView : public View {
void show_max(bool refresh_display = false);
void recon_pause();
void recon_resume();
void frequency_file_load(bool stop_all_before = false);
void frequency_file_load();
void on_statistics_update(const ChannelStatistics& statistics);
void on_index_delta(int32_t v);
void on_stepper_delta(int32_t v);
@@ -93,7 +108,7 @@ class ReconView : public View {
void load_persisted_settings();
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
// placeholder for possible void recon_start_recording();
void recon_stop_recording();
void recon_stop_recording(bool exiting);
// Returns true if 'current_index' is in bounds of frequency_list.
bool current_is_valid();
@@ -125,6 +140,7 @@ class ReconView : public View {
bool load_freqs{true};
bool load_ranges{true};
bool load_hamradios{true};
bool load_repeaters{true};
bool update_ranges{true};
bool fwd{true};
bool recon{true};
@@ -154,8 +170,8 @@ class ReconView : public View {
uint16_t last_nb_match{999};
uint16_t last_freq_lock{999};
size_t last_list_size{0};
int8_t last_rssi_min{-127};
int8_t last_rssi_med{-127};
int8_t last_rssi_min{127};
int8_t last_rssi_med{0};
int8_t last_rssi_max{-127};
int32_t last_index{-1};
int64_t last_freq{0};
@@ -163,6 +179,25 @@ class ReconView : public View {
systime_t chrono_start{};
systime_t chrono_end{};
const std::filesystem::path repeat_rec_file = u"RECON_REPEAT.C16";
const std::filesystem::path repeat_rec_meta = u"RECON_REPEAT.TXT";
const std::filesystem::path repeat_rec_path = u"CAPTURES";
const size_t repeat_read_size{16384};
const size_t repeat_buffer_count{3};
int8_t repeat_cur_rep = 0;
int64_t repeat_sample_rate = 0;
static constexpr uint32_t repeat_bandwidth = 2500000;
void on_repeat_tx_progress(const uint32_t progress);
void start_repeat();
void stop_repeat(const bool do_loop);
bool is_repeat_active() const;
void handle_repeat_thread_done(const uint32_t return_code);
void repeat_file_error(const std::filesystem::path& path, const std::string& message);
std::filesystem::path repeat_file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool repeat_ready_signal{false};
bool recon_tx{false};
// Persisted settings.
SettingsStore ui_settings{
"recon"sv,
@@ -354,17 +389,47 @@ class ReconView : public View {
{168, (35 * 8) - 4, 72, 28},
"<REMOVE>"};
ProgressBar progressbar{
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
/*short_ui*/ true};
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
this->handle_coded_squelch(message.value);
handle_coded_squelch(message.value);
}};
MessageHandlerRegistration message_handler_stats{
Message::ID::ChannelStatistics,
[this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* p) {
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
handle_repeat_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* p) {
auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
repeat_ready_signal = true;
}
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* p) {
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
on_repeat_tx_progress(message.progress);
}};
};
+60 -16
View File
@@ -39,10 +39,10 @@ namespace ui {
ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, std::string input_file, std::string output_file)
: View(parent_rect), _input_file{input_file}, _output_file{output_file} {
hidden(true);
add_children({&button_load_freqs,
add_children({&button_input_file,
&text_input_file,
&button_save_freqs,
&button_output_file,
&button_choose_output_file,
&button_choose_output_name,
&checkbox_autosave_freqs,
&checkbox_autostart_recon,
&checkbox_clear_output});
@@ -52,9 +52,9 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
checkbox_clear_output.set_value(persistent_memory::recon_clear_output());
text_input_file.set(_input_file);
button_output_file.set_text(_output_file);
button_choose_output_name.set_text(_output_file);
button_load_freqs.on_select = [this, &nav](Button&) {
button_input_file.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
@@ -67,25 +67,24 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
};
};
button_save_freqs.on_select = [this, &nav](Button&) {
button_choose_output_file.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
if (new_file_path.native().find(freqman_dir.native()) == 0) {
_output_file = new_file_path.stem().string();
button_output_file.set_text(_output_file);
button_choose_output_name.set_text(_output_file);
} else {
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
nav.display_modal("SAVE ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
}
};
};
button_output_file.on_select = [this, &nav](Button&) {
text_prompt(nav, _output_file, 28,
[this](std::string& buffer) {
_output_file = std::move(buffer);
button_output_file.set_text(_output_file);
});
button_choose_output_name.on_select = [this, &nav](Button&) {
text_prompt(nav, _output_file, 28, [this](std::string& buffer) {
_output_file = buffer;
button_choose_output_name.set_text(_output_file);
});
};
};
@@ -100,12 +99,18 @@ void ReconSetupViewMore::save() {
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
persistent_memory::set_recon_load_repeaters(checkbox_load_repeaters.value());
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
persistent_memory::set_recon_repeat_recorded(checkbox_repeat_recorded.value());
persistent_memory::set_recon_repeat_nb(field_repeat_nb.value());
persistent_memory::set_recon_repeat_amp(checkbox_repeat_amp.value());
persistent_memory::set_recon_repeat_gain(field_repeat_gain.value());
persistent_memory::set_recon_repeat_delay(field_repeat_delay.value());
};
void ReconSetupViewMain::focus() {
button_load_freqs.focus();
button_input_file.focus();
}
ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
@@ -114,16 +119,55 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
hidden(true);
add_children({&checkbox_load_freqs,
&checkbox_load_repeaters,
&checkbox_load_ranges,
&checkbox_load_hamradios,
&checkbox_update_ranges_when_recon,
&checkbox_auto_record_locked});
&checkbox_auto_record_locked,
&checkbox_repeat_recorded,
&text_repeat_nb,
&field_repeat_nb,
&checkbox_repeat_amp,
&text_repeat_gain,
&field_repeat_gain,
&text_repeat_delay,
&field_repeat_delay});
// tx options have to be in yellow to inform the users that activating them will make the device transmit
checkbox_repeat_recorded.set_style(&Styles::yellow);
text_repeat_nb.set_style(&Styles::yellow);
field_repeat_nb.set_style(&Styles::yellow);
checkbox_repeat_amp.set_style(&Styles::yellow);
text_repeat_gain.set_style(&Styles::yellow);
field_repeat_gain.set_style(&Styles::yellow);
text_repeat_delay.set_style(&Styles::yellow);
field_repeat_delay.set_style(&Styles::yellow);
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
checkbox_load_repeaters.set_value(persistent_memory::recon_load_repeaters());
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
checkbox_repeat_recorded.set_value(persistent_memory::recon_repeat_recorded());
checkbox_repeat_amp.set_value(persistent_memory::recon_repeat_amp());
field_repeat_nb.set_value(persistent_memory::recon_repeat_nb());
field_repeat_gain.set_value(persistent_memory::recon_repeat_gain());
field_repeat_delay.set_value(persistent_memory::recon_repeat_delay());
// tx warning modal
checkbox_repeat_recorded.on_select = [this, &nav](Checkbox&, bool v) {
if (v) {
nav.display_modal(
"TX WARNING",
"This activate TX ability\nin Recon !",
YESNO,
[this, &nav](bool choice) {
if (!choice)
checkbox_repeat_recorded.set_value(choice);
});
}
};
};
void ReconSetupViewMore::focus() {
@@ -30,6 +30,7 @@
#include "ui_tabview.hpp"
#include "ui_navigation.hpp"
#include "string_format.hpp"
#include "ui_styles.hpp"
// 1Mhz helper
#ifdef OneMHz
@@ -73,17 +74,18 @@ class ReconSetupViewMain : public View {
std::string _input_file{"RECON"};
std::string _output_file{"RECON_RESULTS"};
Button button_load_freqs{
Button button_input_file{
{1 * 8, 12, 18 * 8, 22},
"select input file"};
Text text_input_file{
{1 * 8, 4 + 2 * 16, 18 * 8, 22},
"RECON"};
Button button_save_freqs{
Button button_choose_output_file{
{1 * 8, 4 * 16 - 8, 18 * 8, 22},
"select output file"};
Button button_output_file{
Button button_choose_output_name{
{1 * 8, 5 * 16 - 2, 18 * 8, 22},
"RECON_RESULTS"};
@@ -115,26 +117,82 @@ class ReconSetupViewMore : public View {
Checkbox checkbox_load_freqs{
{1 * 8, 12},
3,
"input: load freqs"};
"load freq",
true};
Checkbox checkbox_load_repeaters{
{14 * 8, 12},
3,
"load repeater",
true};
Checkbox checkbox_load_ranges{
{1 * 8, 42},
3,
"input: load ranges"};
"load range",
true};
Checkbox checkbox_load_hamradios{
{1 * 8, 72},
3,
"input: load hamradios"};
"load hamradio",
true};
Checkbox checkbox_update_ranges_when_recon{
{1 * 8, 102},
3,
"auto update m-ranges"};
Checkbox checkbox_auto_record_locked{
{1 * 8, 132},
3,
"record locked periods"};
Checkbox checkbox_repeat_recorded{
{1 * 8, 162},
3,
"repeater,"};
Text text_repeat_nb{
{14 * 8, 165, 3 * 8, 22},
"nb:"};
NumberField field_repeat_nb{
{17 * 8, 165},
2,
{1, 99},
1,
' ',
};
Checkbox checkbox_repeat_amp{
{1 * 8, 192},
3,
"AMP,"};
Text text_repeat_gain{
{9 * 8, 196, 5 * 8, 22},
"GAIN:"};
NumberField field_repeat_gain{
{14 * 8, 196},
2,
{0, 47},
1,
' ',
};
Text text_repeat_delay{
{16 * 8, 196, 8 * 8, 22},
", delay:"};
NumberField field_repeat_delay{
{24 * 8, 196},
3,
{0, 254},
1,
' ',
};
};
class ReconSetupView : public View {
+3 -1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -275,7 +276,7 @@ ScannerView::~ScannerView() {
}
void ScannerView::show_max_index() { // show total number of freqs to scan
field_current_index.set_text("---");
field_current_index.set_text("<->");
if (entries.size() == FREQMAN_MAX_PER_FILE) {
text_max_index.set_style(&Styles::red);
@@ -564,6 +565,7 @@ void ScannerView::frequency_file_load(const fs::path& path) {
def_step_index = entry.step;
switch (entry.type) {
case freqman_type::Repeater:
case freqman_type::Single:
entries.push_back({entry.frequency_a, entry.description});
break;
+1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
+269 -50
View File
@@ -3,6 +3,8 @@
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyleft (ɔ) 2024 zxkmm under GPL license
*
* This file is part of PortaPack.
*
@@ -27,6 +29,7 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_touch_calibration.hpp"
#include "ui_text_editor.hpp"
#include "portapack_persistent_memory.hpp"
#include "lpc43xx_cpp.hpp"
@@ -41,7 +44,9 @@ namespace fs = std::filesystem;
#include "string_format.hpp"
#include "ui_styles.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "cpld_update.hpp"
#include "config_mode.hpp"
namespace pmem = portapack::persistent_memory;
@@ -59,10 +64,9 @@ SetDateTimeView::SetDateTimeView(
NavigationView& nav) {
button_save.on_select = [&nav, this](Button&) {
const auto model = this->form_collect();
const rtc::RTC new_datetime{
model.year, model.month, model.day,
model.hour, model.minute, model.second};
rtcSetTime(&RTCD1, &new_datetime);
rtc::RTC new_datetime{model.year, model.month, model.day, model.hour, model.minute, model.second};
pmem::set_config_dst(model.dst);
rtc_time::set(new_datetime); // NB: 1 hour will be subtracted if value is stored in RTC during DST
nav.pop();
},
@@ -78,20 +82,57 @@ SetDateTimeView::SetDateTimeView(
&field_hour,
&field_minute,
&field_second,
&text_weekday,
&text_day_of_year,
&text_in_dst_range,
&checkbox_dst_enable,
&options_dst_start_which,
&options_dst_start_weekday,
&options_dst_start_month,
&options_dst_end_which,
&options_dst_end_weekday,
&options_dst_end_month,
&button_save,
&button_cancel,
});
// Populate DST options (same string text for start & end)
options_dst_start_which.set_options(which_options);
options_dst_end_which.set_options(which_options);
options_dst_start_weekday.set_options(weekday_options);
options_dst_end_weekday.set_options(weekday_options);
options_dst_start_month.set_options(month_options);
options_dst_end_month.set_options(month_options);
const auto dst_changed_fn = [this](size_t, uint32_t) {
handle_date_field_update();
};
const auto date_changed_fn = [this](int32_t) {
handle_date_field_update();
};
field_year.on_change = date_changed_fn;
field_month.on_change = date_changed_fn;
field_day.on_change = date_changed_fn;
options_dst_start_which.on_change = dst_changed_fn;
options_dst_start_weekday.on_change = dst_changed_fn;
options_dst_start_month.on_change = dst_changed_fn;
options_dst_end_which.on_change = dst_changed_fn;
options_dst_end_weekday.on_change = dst_changed_fn;
options_dst_end_month.on_change = dst_changed_fn;
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
SetDateTimeModel model{
datetime.year(),
datetime.month(),
datetime.day(),
datetime.hour(),
datetime.minute(),
datetime.second()};
datetime.second(),
pmem::config_dst()};
form_init(model);
}
@@ -106,6 +147,13 @@ void SetDateTimeView::form_init(const SetDateTimeModel& model) {
field_hour.set_value(model.hour);
field_minute.set_value(model.minute);
field_second.set_value(model.second);
checkbox_dst_enable.set_value(model.dst.b.dst_enabled);
options_dst_start_which.set_by_value(model.dst.b.start_which);
options_dst_start_weekday.set_by_value(model.dst.b.start_weekday);
options_dst_start_month.set_by_value(model.dst.b.start_month);
options_dst_end_which.set_by_value(model.dst.b.end_which);
options_dst_end_weekday.set_by_value(model.dst.b.end_weekday);
options_dst_end_month.set_by_value(model.dst.b.end_month);
}
SetDateTimeModel SetDateTimeView::form_collect() {
@@ -115,7 +163,29 @@ SetDateTimeModel SetDateTimeView::form_collect() {
.day = static_cast<uint8_t>(field_day.value()),
.hour = static_cast<uint8_t>(field_hour.value()),
.minute = static_cast<uint8_t>(field_minute.value()),
.second = static_cast<uint8_t>(field_second.value())};
.second = static_cast<uint8_t>(field_second.value()),
.dst = dst_collect()};
}
pmem::dst_config_t SetDateTimeView::dst_collect() {
pmem::dst_config_t dst;
dst.b.dst_enabled = static_cast<uint8_t>(checkbox_dst_enable.value());
dst.b.start_which = static_cast<uint8_t>(options_dst_start_which.selected_index_value());
dst.b.start_weekday = static_cast<uint8_t>(options_dst_start_weekday.selected_index_value());
dst.b.start_month = static_cast<uint8_t>(options_dst_start_month.selected_index_value());
dst.b.end_which = static_cast<uint8_t>(options_dst_end_which.selected_index_value());
dst.b.end_weekday = static_cast<uint8_t>(options_dst_end_weekday.selected_index_value());
dst.b.end_month = static_cast<uint8_t>(options_dst_end_month.selected_index_value());
return dst;
}
void SetDateTimeView::handle_date_field_update() {
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
text_in_dst_range.set(checkbox_dst_enable.value() && rtc_time::dst_test_date_range(field_year.value(), doy, dst_collect()) ? "DST" : "");
}
/* SetRadioView ******************************************/
@@ -133,7 +203,6 @@ SetRadioView::SetRadioView(
&check_clkout,
&field_clkout_freq,
&labels_clkout_khz,
&value_freq_step,
&labels_bias,
&check_bias,
&disable_external_tcxo, // TODO: always show?
@@ -150,23 +219,10 @@ SetRadioView::SetRadioView(
});
}
std::string source_name("---");
switch (reference.source) {
case ClockManager::ReferenceSource::Xtal:
source_name = "HackRF";
break;
case ClockManager::ReferenceSource::PortaPack:
source_name = "PortaPack";
break;
case ClockManager::ReferenceSource::External:
source_name = "External";
break;
}
std::string source_name = clock_manager.get_source();
value_source.set(source_name);
value_source_frequency.set(
to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
value_source_frequency.set(clock_manager.get_freq());
// Make these Text controls look like Labels.
label_source.set_style(&Styles::light_grey);
@@ -185,29 +241,20 @@ SetRadioView::SetRadioView(
send_system_refresh();
};
field_clkout_freq.set_value(pmem::clkout_freq());
value_freq_step.set_style(&Styles::light_grey);
// Disallow CLKOUT freq change on hackrf_r9 due to dependencies on GP_CLKIN (same Si5351A clock);
// see comments in ClockManager::enable_clock_output()
if (hackrf_r9) {
if (pmem::clkout_freq() != 10000)
pmem::set_clkout_freq(10000);
field_clkout_freq.set_focusable(false);
}
field_clkout_freq.on_select = [this](NumberField&) {
freq_step_khz++;
if (freq_step_khz > 3) {
freq_step_khz = 0;
}
switch (freq_step_khz) {
case 0:
value_freq_step.set(" |");
break;
case 1:
value_freq_step.set(" | ");
break;
case 2:
value_freq_step.set(" | ");
break;
case 3:
value_freq_step.set("| ");
break;
}
field_clkout_freq.set_step(pow(10, freq_step_khz));
field_clkout_freq.set_value(pmem::clkout_freq());
field_clkout_freq.on_change = [this](SymField&) {
if (field_clkout_freq.to_integer() < 4) // Min. CLK out of Si5351A/B/C-B is 2.5khz , but in our application -intermediate freq 800Mhz-,Min working CLK=4khz.
field_clkout_freq.set_value(4);
if (field_clkout_freq.to_integer() > 60000)
field_clkout_freq.set_value(60000);
};
check_bias.set_value(get_antenna_bias());
@@ -248,7 +295,7 @@ void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) {
SetFrequencyCorrectionModel SetRadioView::form_collect() {
return {
.ppm = static_cast<int8_t>(field_ppm.value()),
.freq = static_cast<uint32_t>(field_clkout_freq.value()),
.freq = static_cast<uint32_t>(field_clkout_freq.to_integer()),
};
}
@@ -270,6 +317,7 @@ SetUIView::SetUIView(NavigationView& nav) {
&toggle_bias_tee,
&toggle_clock,
&toggle_mute,
&toggle_fake_brightness,
&toggle_sd_card,
&button_save,
&button_cancel});
@@ -303,6 +351,7 @@ SetUIView::SetUIView(NavigationView& nav) {
toggle_clock.set_value(!pmem::ui_hide_clock());
toggle_speaker.set_value(!pmem::ui_hide_speaker());
toggle_mute.set_value(!pmem::ui_hide_mute());
toggle_fake_brightness.set_value(!pmem::ui_hide_fake_brightness());
toggle_sd_card.set_value(!pmem::ui_hide_sd_card());
button_save.on_select = [&nav, this](Button&) {
@@ -329,6 +378,7 @@ SetUIView::SetUIView(NavigationView& nav) {
pmem::set_ui_hide_clock(!toggle_clock.value());
pmem::set_ui_hide_speaker(!toggle_speaker.value());
pmem::set_ui_hide_mute(!toggle_mute.value());
pmem::set_ui_hide_fake_brightness(!toggle_fake_brightness.value());
pmem::set_ui_hide_sd_card(!toggle_sd_card.value());
send_system_refresh();
@@ -343,6 +393,38 @@ void SetUIView::focus() {
button_save.focus();
}
/* SetSDCardView *********************************************/
SetSDCardView::SetSDCardView(NavigationView& nav) {
add_children({&labels,
&checkbox_sdcard_speed,
&button_test_sdcard_high_speed,
&text_sdcard_test_status,
&button_save,
&button_cancel});
checkbox_sdcard_speed.set_value(pmem::config_sdcard_high_speed_io());
button_test_sdcard_high_speed.on_select = [&nav, this](Button&) {
pmem::set_config_sdcard_high_speed_io(true, false);
text_sdcard_test_status.set("!! HIGH SPEED MODE ON !!");
};
button_save.on_select = [&nav, this](Button&) {
pmem::set_config_sdcard_high_speed_io(checkbox_sdcard_speed.value(), true);
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetSDCardView::focus() {
button_save.focus();
}
/* SetConverterSettingsView ******************************/
SetConverterSettingsView::SetConverterSettingsView(NavigationView& nav) {
@@ -427,7 +509,7 @@ SetFrequencyCorrectionView::SetFrequencyCorrectionView(NavigationView& nav) {
pmem::set_freq_rx_correction_updown(v);
};
opt_tx_correction_mode.set_by_value(pmem::config_freq_rx_correction_updown());
opt_tx_correction_mode.set_by_value(pmem::config_freq_tx_correction_updown());
opt_tx_correction_mode.on_change = [this](size_t, OptionsField::value_t v) {
pmem::set_freq_tx_correction_updown(v);
};
@@ -531,6 +613,8 @@ SetPersistentMemoryView::SetPersistentMemoryView(NavigationView& nav) {
[this](bool choice) {
if (choice) {
pmem::cache::defaults();
// Refresh status bar
send_system_refresh();
}
});
};
@@ -600,13 +684,16 @@ void SetQRCodeView::focus() {
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
add_children({&labels,
&field_encoder_dial_sensitivity,
&field_encoder_rate_multiplier,
&button_save,
&button_cancel});
field_encoder_dial_sensitivity.set_by_value(pmem::config_encoder_dial_sensitivity());
field_encoder_dial_sensitivity.set_by_value(pmem::encoder_dial_sensitivity());
field_encoder_rate_multiplier.set_value(pmem::encoder_rate_multiplier());
button_save.on_select = [&nav, this](Button&) {
pmem::set_encoder_dial_sensitivity(field_encoder_dial_sensitivity.selected_index_value());
pmem::set_encoder_rate_multiplier(field_encoder_rate_multiplier.value());
nav.pop();
};
@@ -619,6 +706,133 @@ void SetEncoderDialView::focus() {
button_save.focus();
}
/* AppSettingsView ************************************/
AppSettingsView::AppSettingsView(
NavigationView& nav)
: nav_{nav} {
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
ensure_directory(SETTINGS_DIR);
for (const auto& entry : std::filesystem::directory_iterator(SETTINGS_DIR, u"*.ini")) {
auto path = (std::filesystem::path)SETTINGS_DIR / entry.path();
menu_view.add_item({path.filename().string().substr(0, 26),
ui::Color::dark_cyan(),
&bitmap_icon_file_text,
[this, path](KeyEvent) {
nav_.push<TextEditorView>(path);
}});
}
}
void AppSettingsView::focus() {
menu_view.focus();
}
/* SetConfigModeView ************************************/
SetConfigModeView::SetConfigModeView(NavigationView& nav) {
add_children({&labels,
&checkbox_config_mode_enabled,
&button_save,
&button_cancel});
checkbox_config_mode_enabled.set_value(!pmem::config_disable_config_mode());
button_save.on_select = [&nav, this](Button&) {
pmem::set_config_disable_config_mode(!checkbox_config_mode_enabled.value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetConfigModeView::focus() {
button_save.focus();
}
/* SetFakeBrightnessView ************************************/
SetFakeBrightnessView::SetFakeBrightnessView(NavigationView& nav) {
add_children({&labels,
&field_fake_brightness,
&button_save,
&button_cancel,
&checkbox_brightness_switch});
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
button_save.on_select = [&nav, this](Button&) {
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetFakeBrightnessView::focus() {
button_save.focus();
}
/* SetMenuColorView ************************************/
void SetMenuColorView::paint_sample() {
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
button_sample.set_bg_color(c);
}
SetMenuColorView::SetMenuColorView(NavigationView& nav) {
add_children({&labels,
&button_sample,
&field_red_level,
&field_green_level,
&field_blue_level,
&button_save,
&button_cancel});
button_sample.set_focusable(false);
Color c = pmem::menu_color();
field_red_level.set_value(c.r());
field_green_level.set_value(c.g());
field_blue_level.set_value(c.b());
paint_sample();
const auto color_changed_fn = [this](int32_t) {
paint_sample();
};
field_red_level.on_change = color_changed_fn;
field_green_level.on_change = color_changed_fn;
field_blue_level.on_change = color_changed_fn;
button_save.on_select = [&nav, this](Button&) {
Color c = Color(field_red_level.value(), field_green_level.value(), field_blue_level.value());
pmem::set_menu_color(c);
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetMenuColorView::focus() {
button_save.focus();
}
/* SettingsMenuView **************************************/
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
@@ -626,16 +840,21 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<AppSettingsView>(); }},
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [&nav]() { nav.push<SetConfigModeView>(); }},
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetRadioView>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SetSDCardView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
{"Brightness", ui::Color::dark_cyan(), &bitmap_icon_brightness, [&nav]() { nav.push<SetFakeBrightnessView>(); }},
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [&nav]() { nav.push<SetMenuColorView>(); }},
});
set_max_rows(2); // allow wider buttons
}
+285 -31
View File
@@ -3,6 +3,8 @@
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyleft (ɔ) 2024 zxkmm under GPL license
*
* This file is part of PortaPack.
*
@@ -44,6 +46,7 @@ struct SetDateTimeModel {
uint8_t hour;
uint8_t minute;
uint8_t second;
portapack::persistent_memory::dst_config_t dst;
};
class SetDateTimeView : public View {
@@ -55,56 +58,114 @@ class SetDateTimeView : public View {
std::string title() const override { return "Date/Time"; };
private:
using option_t = std::pair<std::string, int32_t>;
std::vector<option_t> which_options = {{"1st", 0}, {"2nd", 1}, {"3rd", 2}, {"4th", 3}, {"Last", 4}};
std::vector<option_t> weekday_options = {{"Sun", 0}, {"Mon", 1}, {"Tue", 2}, {"Wed", 3}, {"Thu", 4}, {"Fri", 5}, {"Sat", 6}};
std::vector<option_t> month_options = {{"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4}, {"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8}, {"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12}};
Labels labels{
{{1 * 8, 1 * 16}, "Adjust the RTC clock date &", Color::light_grey()},
{{1 * 8, 2 * 16}, "time. If clock resets after", Color::light_grey()},
{{1 * 8, 3 * 16}, "reboot, coin batt. is dead. ", Color::light_grey()},
{{5 * 8, 8 * 16 - 2}, "YYYY-MM-DD HH:MM:SS", Color::grey()},
{{9 * 8, 9 * 16}, "- - : :", Color::light_grey()}};
{{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Color::grey()},
{{5 * 8, 6 * 16}, "- - : :", Color::light_grey()},
{{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Color::light_grey()},
{{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Color::light_grey()},
{{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Color::light_grey()}};
NumberField field_year{
{5 * 8, 9 * 16},
{1 * 8, 6 * 16},
4,
{2015, 2099},
1,
'0',
true,
};
NumberField field_month{
{10 * 8, 9 * 16},
{6 * 8, 6 * 16},
2,
{1, 12},
1,
'0',
true,
};
NumberField field_day{
{13 * 8, 9 * 16},
{9 * 8, 6 * 16},
2,
{1, 31},
1,
'0',
true,
};
NumberField field_hour{
{16 * 8, 9 * 16},
{12 * 8, 6 * 16},
2,
{0, 23},
1,
'0',
true,
};
NumberField field_minute{
{19 * 8, 9 * 16},
{15 * 8, 6 * 16},
2,
{0, 59},
1,
'0',
true,
};
NumberField field_second{
{22 * 8, 9 * 16},
{18 * 8, 6 * 16},
2,
{0, 59},
1,
'0',
true,
};
Text text_weekday{
{22 * 8, 6 * 16, 3 * 8, 16},
""};
Text text_day_of_year{
{26 * 8, 6 * 16, 3 * 8, 16},
""};
Text text_in_dst_range{
{17 * 8, 7 * 16, 3 * 8, 16},
""};
Checkbox checkbox_dst_enable{
{2 * 8, 9 * 16},
23,
"Enable Daylight Savings"};
OptionsField options_dst_start_which{
{15 * 8, 12 * 16},
4,
{}};
OptionsField options_dst_start_weekday{
{20 * 8, 12 * 16},
3,
{}};
OptionsField options_dst_start_month{
{27 * 8, 12 * 16},
3,
{}};
OptionsField options_dst_end_which{
{15 * 8, 13 * 16},
4,
{}};
OptionsField options_dst_end_weekday{
{20 * 8, 13 * 16},
3,
{}};
OptionsField options_dst_end_month{
{27 * 8, 13 * 16},
3,
{}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -115,6 +176,8 @@ class SetDateTimeView : public View {
void form_init(const SetDateTimeModel& model);
SetDateTimeModel form_collect();
portapack::persistent_memory::dst_config_t dst_collect();
void handle_date_field_update();
};
struct SetFrequencyCorrectionModel {
@@ -163,20 +226,14 @@ class SetRadioView : public View {
13,
"Enable CLKOUT"};
NumberField field_clkout_freq{
SymField field_clkout_freq{
{20 * 8, 6 * 16},
5,
{10, 60000},
1000,
' '};
SymField::Type::Dec};
Labels labels_clkout_khz{
{{26 * 8, 6 * 16}, "kHz", Color::light_grey()}};
Text value_freq_step{
{21 * 8, (7 * 16), 4 * 8, 16},
"| "};
Labels labels_bias{
{{4 * 8 + 4, 8 * 16}, "CAUTION: Ensure that all", Color::red()},
{{5 * 8 + 0, 9 * 16}, "devices attached to the", Color::red()},
@@ -265,39 +322,43 @@ class SetUIView : public View {
};
ImageToggle toggle_camera{
{7 * 8, 14 * 16 + 2, 16, 16},
{6 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_camera};
ImageToggle toggle_sleep{
{9 * 8, 14 * 16 + 2, 16, 16},
{8 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_sleep};
ImageToggle toggle_stealth{
{11 * 8, 14 * 16 + 2, 16, 16},
{10 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_stealth};
ImageToggle toggle_converter{
{13 * 8, 14 * 16 + 2, 16, 16},
{12 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_upconvert};
ImageToggle toggle_bias_tee{
{15 * 8, 14 * 16 + 2, 16, 16},
{14 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_biast_off};
ImageToggle toggle_clock{
{17 * 8, 14 * 16 + 2, 8, 16},
{16 * 8, 14 * 16 + 2, 8, 16},
&bitmap_icon_clk_ext};
ImageToggle toggle_mute{
{18 * 8, 14 * 16 + 2, 16, 16},
{17 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_and_headphones_mute};
ImageToggle toggle_speaker{
{20 * 8, 14 * 16 + 2, 16, 16},
{19 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_speaker_mute};
ImageToggle toggle_fake_brightness{
{21 * 8, 14 * 16 + 2, 16, 16},
&bitmap_icon_brightness};
ImageToggle toggle_sd_card{
{22 * 8, 14 * 16 + 2, 16, 16},
{23 * 8, 14 * 16 + 2, 16, 16},
&bitmap_sd_card_ok};
Button button_save{
@@ -306,8 +367,43 @@ class SetUIView : public View {
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
"Cancel"};
};
class SetSDCardView : public View {
public:
SetSDCardView(NavigationView& nav);
void focus() override;
std::string title() const override { return "SD Card"; };
private:
Labels labels{
// 01234567890123456789012345678
{{1 * 8, 120 - 48}, " HIGH SPEED SDCARD IO ", Color::light_grey()},
{{1 * 8, 120 - 32}, " May or may not work !! ", Color::light_grey()}};
Checkbox checkbox_sdcard_speed{
{2 * 8, 120},
20,
"enable high speed IO"};
Button button_test_sdcard_high_speed{
{2 * 8, 152, 27 * 8, 32},
"TEST BUTTON (NO PMEM SAVE)"};
Text text_sdcard_test_status{
{2 * 8, 198, 28 * 8, 16},
""};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel"};
};
class SetConverterSettingsView : public View {
@@ -460,6 +556,7 @@ class SetQRCodeView : public View {
};
using portapack::persistent_memory::encoder_dial_sensitivity;
using portapack::persistent_memory::encoder_rate_multiplier;
class SetEncoderDialView : public View {
public:
@@ -471,18 +568,30 @@ class SetEncoderDialView : public View {
private:
Labels labels{
{{1 * 8, 1 * 16}, "Adjusts how many steps to", Color::light_grey()},
{{1 * 8, 2 * 16}, "change the encoder value.", Color::light_grey()},
{{2 * 8, 4 * 16}, "Dial sensitivity:", Color::light_grey()},
{{1 * 8, 1 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
{{1 * 8, 2 * 16}, "rotation position (number of", Color::light_grey()},
{{1 * 8, 3 * 16}, "steps per full rotation):", Color::light_grey()},
{{2 * 8, 5 * 16}, "Dial sensitivity:", Color::light_grey()},
{{1 * 8, 8 * 16}, "Adjusts sensitivity to dial", Color::light_grey()},
{{1 * 8, 9 * 16}, "rotation rate (default 1", Color::light_grey()},
{{1 * 8, 10 * 16}, "means no rate dependency):", Color::light_grey()},
{{3 * 8, 12 * 16}, "Rate multiplier:", Color::light_grey()},
};
OptionsField field_encoder_dial_sensitivity{
{20 * 8, 4 * 16},
{20 * 8, 5 * 16},
6,
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
NumberField field_encoder_rate_multiplier{
{20 * 8, 12 * 16},
2,
{1, 15},
1,
' '};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
@@ -535,6 +644,151 @@ class SetPersistentMemoryView : public View {
};
};
class AppSettingsView : public View {
public:
AppSettingsView(NavigationView& nav);
std::string title() const override { return "App Settings"; };
void focus() override;
private:
NavigationView& nav_;
Labels labels{
{{0, 4}, "Select file to edit:", Color::white()}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
true};
};
class SetConfigModeView : public View {
public:
SetConfigModeView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Config Mode"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Controls whether firmware", Color::light_grey()},
{{1 * 8, 2 * 16}, "will enter Config Mode", Color::light_grey()},
{{1 * 8, 3 * 16}, "after a boot failure.", Color::light_grey()},
};
Checkbox checkbox_config_mode_enabled{
{2 * 8, 6 * 16},
16,
"Config Mode enable"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
using portapack::persistent_memory::fake_brightness_level_options;
class SetFakeBrightnessView : public View {
public:
SetFakeBrightnessView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Brightness"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Limits screen brightness", Color::light_grey()},
{{1 * 8, 2 * 16}, "(has a small performance", Color::light_grey()},
{{1 * 8, 3 * 16}, "impact when enabled).", Color::light_grey()},
{{2 * 8, 8 * 16}, "Brightness:", Color::light_grey()},
};
OptionsField field_fake_brightness{
{20 * 8, 8 * 16},
6,
{{"12.5%", fake_brightness_level_options::BRIGHTNESS_12p5},
{"25%", fake_brightness_level_options::BRIGHTNESS_25},
{"50%", fake_brightness_level_options::BRIGHTNESS_50}}};
Checkbox checkbox_brightness_switch{
{1 * 8, 5 * 16},
16,
"Enable brightness adjust"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SetMenuColorView : public View {
public:
SetMenuColorView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Menu Color"; };
private:
void paint_sample();
Labels labels{
{{3 * 8, 1 * 16}, "Menu Button Color Scheme", Color::light_grey()},
{{2 * 8, 8 * 16}, "Red Level:", Color::light_grey()},
{{2 * 8, 9 * 16}, "Green Level:", Color::light_grey()},
{{2 * 8, 10 * 16}, "Blue Level:", Color::light_grey()},
};
NewButton button_sample{
{8 * 8, 4 * 16, 14 * 8, 3 * 16},
"New Color",
&bitmap_icon_brightness,
};
NumberField field_red_level{
{15 * 8, 8 * 16},
3,
{8, 248},
8,
' ',
};
NumberField field_green_level{
{15 * 8, 9 * 16},
3,
{8, 248},
8,
' ',
};
NumberField field_blue_level{
{15 * 8, 10 * 16},
3,
{8, 248},
8,
' ',
};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SettingsMenuView : public BtnGridView {
public:
SettingsMenuView(NavigationView& nav);
+8
View File
@@ -87,10 +87,18 @@ SigGenView::SigGenView(
&field_stop,
&tx_view});
symfield_tone.hidden(1); // At first launch , by default we are in CW Shape has NO MOD , we are not using Tone modulation.
symfield_tone.set_value(1000); // Default: 1000 Hz
options_shape.on_change = [this](size_t, OptionsField::value_t v) {
text_shape.set(shape_strings[v]);
if (auto_update)
update_config();
if ((v == 0) || (v == 6)) { // In Shapes Options (CW & Pseudo Random Noise) we are not using Tone modulation freq.
symfield_tone.hidden(1);
} else {
symfield_tone.hidden(0);
}
set_dirty();
};
options_shape.set_selected_index(0);
text_shape.set(shape_strings[0]);
+14 -11
View File
@@ -58,15 +58,16 @@ class SigGenView : public View {
app_settings::SettingsManager settings_{
"tx_siggen", app_settings::Mode::TX};
const std::string shape_strings[7] = {
"CW-just carrier",
"Sine signal ",
"Triangle signal",
"Saw up signal ",
"Saw down signal",
"Square signal ",
"Noise signal " // using 16 bits LFSR register, 16 order polynomial feedback.
};
const std::string shape_strings[9] = {
"CW (No mod.) ",
"Sine mod. FM",
"Triangle mod.FM", // max 15 character text space.
"Saw up mod. FM",
"Saw down mod.FM",
"Square mod. FM",
"Pseudo Noise FM", // using 16 bits LFSR register, 16 order polynomial feedback.
"BPSK 0,1,0,1...",
"QPSK 00-01-10.."};
bool auto_update{false};
@@ -86,10 +87,12 @@ class SigGenView : public View {
{&bitmap_sig_saw_up, 3},
{&bitmap_sig_saw_down, 4},
{&bitmap_sig_square, 5},
{&bitmap_sig_noise, 6}}};
{&bitmap_sig_noise, 6},
{&bitmap_sig_noise, 7}, // Pending to add a correct BPSK icon.
{&bitmap_sig_noise, 8}}}; // Pending to add a correct QPSK icon.
Text text_shape{
{15 * 8, 4 + 10, 8 * 8, 16},
{15 * 8, 4 + 10, 15 * 8, 16},
""};
SymField symfield_tone{
+16 -1
View File
@@ -90,13 +90,17 @@ SondeView::SondeView(NavigationView& nav)
};
button_see_map.on_select = [this, &nav](Button&) {
nav.push<GeoMapView>(
geomap_view_ = nav.push<GeoMapView>(
sonde_id,
gps_info.alt,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN,
gps_info.lat,
gps_info.lon,
999); // set a dummy heading out of range to draw a cross...probably not ideal?
nav.set_on_pop([this]() {
geomap_view_ = nullptr;
});
};
logger = std::make_unique<SondeLogger>();
@@ -123,6 +127,17 @@ SondeView::~SondeView() {
audio::output::stop();
}
void SondeView::on_gps(const GPSPosDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
}
void SondeView::on_orientation(const OrientationDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_orientation(msg->angle, true);
}
void SondeView::focus() {
field_frequency.focus();
}
+21 -1
View File
@@ -60,6 +60,8 @@ class SondeView : public View {
SondeView(NavigationView& nav);
~SondeView();
SondeView(const SondeView& other) = delete;
SondeView& operator=(const SondeView& other) = delete;
void focus() override;
@@ -162,7 +164,8 @@ class SondeView : public View {
GeoPos geopos{
{0, 12 * 16},
GeoPos::alt_unit::METERS};
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN};
Button button_see_qr{
{2 * 8, 15 * 16, 12 * 8, 3 * 16},
@@ -172,6 +175,8 @@ class SondeView : public View {
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
"See on map"};
GeoMapView* geomap_view_{nullptr};
MessageHandlerRegistration message_handler_packet{
Message::ID::SondePacket,
[this](Message* const p) {
@@ -180,6 +185,21 @@ class SondeView : public View {
this->on_packet(packet);
}};
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);
}};
void on_gps(const GPSPosDataMessage* msg);
void on_orientation(const OrientationDataMessage* msg);
void on_packet(const sonde::Packet& packet);
char* float_to_char(float x, char* p);
};
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
+242
View File
@@ -0,0 +1,242 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 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_subghzd.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace ui;
namespace ui {
void SubGhzDRecentEntryDetailView::update_data() {
// set text elements
text_type.set(SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)entry_.sensorType));
text_id.set("0x" + to_string_hex(entry_.serial));
if (entry_.bits > 0) console.writeln("Bits: " + to_string_dec_uint(entry_.bits));
if (entry_.btn != SD_NO_BTN) console.writeln("Btn: " + to_string_dec_uint(entry_.btn));
if (entry_.cnt != SD_NO_CNT) console.writeln("Cnt: " + to_string_dec_uint(entry_.cnt));
if (entry_.data != 0) console.writeln("Data: " + to_string_hex(entry_.data));
}
SubGhzDRecentEntryDetailView::SubGhzDRecentEntryDetailView(NavigationView& nav, const SubGhzDRecentEntry& entry)
: nav_{nav},
entry_{entry} {
add_children({&button_done,
&text_type,
&text_id,
&console,
&labels});
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
update_data();
}
void SubGhzDRecentEntryDetailView::focus() {
button_done.focus();
}
void SubGhzDView::focus() {
field_frequency.focus();
}
SubGhzDView::SubGhzDView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&button_clear_list,
&recent_entries_view});
baseband::run_image(portapack::spi_flash::image_tag_subghzd);
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_step(100000);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const SubGhzDRecentEntry& entry) {
nav_.push<SubGhzDRecentEntryDetailView>(entry);
};
baseband::set_subghzd_config(0, receiver_model.sampling_rate()); // 0=am
receiver_model.enable();
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
}
void SubGhzDView::on_tick_second() {
for (auto& entry : recent) {
entry.inc_age(1);
}
recent_entries_view.set_dirty();
}
void SubGhzDView::on_data(const SubGhzDDataMessage* data) {
SubGhzDRecentEntry key{data->sensorType, data->serial, data->bits, data->data, data->btn, data->cnt};
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
// Found within. Move to front of list, increment counter.
(*matching_recent).reset_age();
recent.push_front(*matching_recent);
recent.erase(matching_recent);
} else {
recent.emplace_front(key);
truncate_entries(recent, 64);
}
recent_entries_view.set_dirty();
}
SubGhzDView::~SubGhzDView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
}
const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) {
switch (type) {
case FPS_PRINCETON:
return "Princeton";
case FPS_BETT:
return "Bett";
case FPS_CAME:
return "Came";
case FPS_PRASTEL:
return "Prastel";
case FPS_AIRFORCE:
return "Airforce";
case FPS_CAMEATOMO:
return "Came Atomo";
case FPS_CAMETWEE:
return "Came Twee";
case FPS_CHAMBCODE:
return "Chamb Code";
case FPS_CLEMSA:
return "Clemsa";
case FPS_DOITRAND:
return "Doitrand";
case FPS_DOOYA:
return "Dooya";
case FPS_FAAC:
return "Faac";
case FPS_GATETX:
return "Gate TX";
case FPS_HOLTEK:
return "Holtek";
case FPS_HOLTEKHT12X:
return "Holtek HT12X";
case FPS_HONEYWELL:
return "Honeywell";
case FPS_HONEYWELLWDB:
return "Honeywell Wdb";
case FPS_HORMANN:
return "Hormann";
case FPS_IDO:
return "Ido 11x";
case FPS_INTERTECHNOV3:
return "InterTehcno v3";
case FPS_KEELOQ:
return "KeeLoq";
case FPS_KINGGATESSTYLO4K:
return "Kinggate Stylo4K";
case FPS_LINEAR:
return "Linear";
case FPS_LINEARDELTA3:
return "Linear Delta3";
case FPS_MAGELLAN:
return "Magellan";
case FPS_MARANTEC:
return "Marantec";
case FPS_MASTERCODE:
return "Mastercode";
case FPS_MEGACODE:
return "Megacode";
case FPS_NERORADIO:
return "Nero Radio";
case FPS_NERO_SKETCH:
return "Nero Sketch";
case FPS_NICEFLO:
return "Nice Flo";
case FPS_NICEFLORS:
return "Nice Flor S";
case FPS_PHOENIXV2:
return "Phoenix V2";
case FPS_POWERSMART:
return "PowerSmart";
case FPS_SECPLUSV1:
return "SecPlus V1";
case FPS_SECPLUSV2:
return "SecPlus V2";
case FPS_SMC5326:
return "SMC5326";
case FPS_STARLINE:
return "Star Line";
case FPS_X10:
return "X10";
case FPS_Invalid:
default:
return "Unknown";
}
}
std::string SubGhzDView::pad_string_with_spaces(int snakes) {
std::string paddedStr(snakes, ' ');
return paddedStr;
}
template <>
void RecentEntriesTable<ui::SubGhzDRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
std::string line{};
line.reserve(30);
line = SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)entry.sensorType);
line = line + " " + to_string_hex(entry.serial);
if (line.length() < 19) {
line += SubGhzDView::pad_string_with_spaces(19 - line.length());
} else {
line = truncate(line, 19);
}
std::string ageStr = to_string_dec_uint(entry.age);
std::string bitsStr = to_string_dec_uint(entry.bits);
line += SubGhzDView::pad_string_with_spaces(5 - bitsStr.length()) + bitsStr;
line += SubGhzDView::pad_string_with_spaces(4 - ageStr.length()) + ageStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
} // namespace ui
+171
View File
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 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.
*/
#ifndef __UI_SUBGHZD_H__
#define __UI_SUBGHZD_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/subghztypes.hpp"
using namespace ui;
namespace ui {
struct SubGhzDRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0x0fffffff;
uint8_t sensorType = FPS_Invalid;
uint8_t btn = SD_NO_BTN;
uint32_t serial = SD_NO_SERIAL;
uint16_t bits = 0;
uint16_t age = 0; // updated on each seconds, show how long the signal was last seen
uint32_t cnt = SD_NO_CNT;
uint64_t data = 0;
SubGhzDRecentEntry() {}
SubGhzDRecentEntry(
uint8_t sensorType,
uint32_t serial,
uint16_t bits = 0,
uint64_t data = 0,
uint8_t btn = SD_NO_BTN,
uint32_t cnt = SD_NO_CNT)
: sensorType{sensorType},
btn{btn},
serial{serial},
bits{bits},
cnt{cnt},
data{data} {
}
Key key() const {
return (data ^ ((static_cast<uint64_t>(serial) << 32) | (static_cast<uint64_t>(sensorType) & 0xFF) << 0));
}
void inc_age(int delta) {
if (UINT16_MAX - delta > age) age += delta;
}
void reset_age() {
age = 0;
}
};
using SubGhzDRecentEntries = RecentEntries<SubGhzDRecentEntry>;
using SubGhzDRecentEntriesView = RecentEntriesView<SubGhzDRecentEntries>;
class SubGhzDView : public View {
public:
SubGhzDView(NavigationView& nav);
~SubGhzDView();
void focus() override;
std::string title() const override { return "SubGhzD"; };
static const char* getSensorTypeName(FPROTO_SUBGHZD_SENSOR type);
static std::string pad_string_with_spaces(int snakes);
private:
void on_tick_second();
void on_data(const SubGhzDDataMessage* data);
NavigationView& nav_;
RxRadioState radio_state_{
433'920'000 /* frequency */,
1'750'000 /* bandwidth */,
4'000'000 /* sampling rate */,
ReceiverModel::Mode::AMAudio};
app_settings::SettingsManager settings_{
"rx_subghzd",
app_settings::Mode::RX,
{}};
SubGhzDRecentEntries recent{};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
SignalToken signal_token_tick_second{};
Button button_clear_list{
{0, 16, 7 * 8, 32},
"Clear"};
static constexpr auto header_height = 3 * 16;
const RecentEntriesColumns columns{{
{"Type", 19},
{"Bits", 4},
{"Age", 3},
}};
SubGhzDRecentEntriesView recent_entries_view{columns, recent};
MessageHandlerRegistration message_handler_packet{
Message::ID::SubGhzDData,
[this](Message* const p) {
const auto message = static_cast<const SubGhzDDataMessage*>(p);
this->on_data(message);
}};
};
class SubGhzDRecentEntryDetailView : public View {
public:
SubGhzDRecentEntryDetailView(NavigationView& nav, const SubGhzDRecentEntry& entry);
void update_data();
void focus() override;
private:
NavigationView& nav_;
SubGhzDRecentEntry entry_{};
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"};
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Console console{
{0, 4 * 16, 240, screen_height - (4 * 16) - 36}};
Labels labels{
{{0 * 8, 0 * 16}, "Type:", Color::light_grey()},
{{0 * 8, 2 * 16}, "Serial: ", Color::light_grey()},
{{0 * 8, 3 * 16}, "Data:", Color::light_grey()},
};
Button button_done{
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
"Done"};
};
} // namespace ui
#endif /*__UI_SUBGHZD_H__*/
+82 -2
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -26,6 +27,8 @@
#include "log_file.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -72,6 +75,11 @@ void TextViewer::paint(Painter& painter) {
paint_state_.redraw_text = false;
}
if (paint_state_.redraw_marked) {
paint_marked(painter);
paint_state_.redraw_marked = false;
}
paint_cursor(painter);
}
@@ -118,8 +126,9 @@ bool TextViewer::on_encoder(EncoderEvent delta) {
return updated;
}
void TextViewer::redraw(bool redraw_text) {
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
paint_state_.redraw_text = redraw_text;
paint_state_.redraw_marked = redraw_marked;
set_dirty();
}
@@ -140,6 +149,32 @@ void TextViewer::cursor_end() {
redraw();
}
void TextViewer::cursor_set(uint16_t line, uint16_t col) {
cursor_.line = line;
cursor_.col = col;
}
void TextViewer::cursor_mark_selected() {
LineColPair newMarker = std::make_pair(cursor_.line, cursor_.col);
auto it = std::find(lineColPair.begin(), lineColPair.end(), newMarker);
if (it != lineColPair.end()) {
lineColPair.erase(it);
} else {
lineColPair.push_back(newMarker);
}
// Mark pending change.
cursor_.mark_change = false;
redraw();
}
void TextViewer::cursor_clear_marked() {
lineColPair.clear();
redraw(true, true);
}
uint16_t TextViewer::line_length() {
return file_->line_length(cursor_.line);
}
@@ -247,12 +282,52 @@ void TextViewer::paint_cursor(Painter& painter) {
};
if (paint_state_.line != UINT32_MAX) // only XOR old cursor if it still appears on the screen
xor_cursor(paint_state_.line, paint_state_.col);
{
// Only reset previous cursor if we aren't marking.
if (paint_state_.mark_change) {
xor_cursor(paint_state_.line, paint_state_.col);
}
}
xor_cursor(cursor_.line, cursor_.col);
paint_state_.line = cursor_.line;
paint_state_.col = cursor_.col;
paint_state_.mark_change = cursor_.mark_change;
// Reset marking and wait for new change.
cursor_.mark_change = true;
}
void TextViewer::paint_marked(Painter& painter) {
auto xor_cursor = [this, &painter](int32_t line, uint16_t col) {
int cursor_width = char_width + 1;
int x = (col - paint_state_.first_col) * char_width - 1;
if (x < 0) { // cursor is one pixel narrower when in left column
cursor_width--;
x = 0;
}
int y = screen_rect().top() + (line - paint_state_.first_line) * char_height;
// Converting one row at a time to reduce buffer size
auto pbuf8 = cursor_.pixel_buffer8;
auto pbuf = cursor_.pixel_buffer;
for (auto col = 0; col < char_height; col++) {
// Annoyingly, read_pixels uses a 24-bit pixel format vs draw_pixels which uses 16-bit
portapack::display.read_pixels({x, y + col, cursor_width, 1}, pbuf8, cursor_width);
for (auto i = 0; i < cursor_width; i++)
pbuf[i] = Color(pbuf8[i].r, pbuf8[i].g, pbuf8[i].b).v ^ 0xFFFF;
portapack::display.draw_pixels({x, y + col, cursor_width, 1}, pbuf, cursor_width);
}
};
auto it = lineColPair.begin();
while (it != lineColPair.end()) {
LineColPair entry = (LineColPair)*it;
xor_cursor(entry.first, entry.second);
it++;
}
}
void TextViewer::reset_file(FileWrapper* file) {
@@ -352,6 +427,9 @@ TextEditorView::TextEditorView(NavigationView& nav)
&text_size,
});
text_position.set_style(&Styles::bg_dark_blue);
text_size.set_style(&Styles::bg_dark_blue);
viewer.set_font_zoom(enable_zoom);
viewer.on_select = [this]() {
@@ -478,6 +556,8 @@ void TextEditorView::open_file(const fs::path& path) {
viewer.set_file(*file_);
}
portapack::persistent_memory::set_apply_fake_brightness(false); // work around to resolve the display issue in notepad app. not elegant i know, so TODO.
refresh_ui();
}
+13 -1
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -59,7 +60,7 @@ class TextViewer : public Widget {
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
void redraw(bool redraw_text = false);
void redraw(bool redraw_text = false, bool redraw_marked = false);
void set_file(FileWrapper& file) { reset_file(&file); }
void clear_file() { reset_file(); }
@@ -71,6 +72,12 @@ class TextViewer : public Widget {
void cursor_home();
void cursor_end();
void cursor_set(uint16_t line, uint16_t col);
void cursor_mark_selected();
void cursor_clear_marked();
typedef std::pair<uint16_t, uint16_t> LineColPair;
std::vector<LineColPair> lineColPair{};
// Gets the length of the current line.
uint16_t line_length();
@@ -97,6 +104,7 @@ class TextViewer : public Widget {
void paint_text(Painter& painter, uint32_t line, uint16_t col);
void paint_cursor(Painter& painter);
void paint_marked(Painter& painter);
void reset_file(FileWrapper* file = nullptr);
@@ -111,6 +119,8 @@ class TextViewer : public Widget {
uint32_t first_line{};
uint16_t first_col{};
bool redraw_text{true};
bool redraw_marked{false};
bool mark_change{true};
} paint_state_{};
struct {
@@ -121,6 +131,8 @@ class TextViewer : public Widget {
// Pixel buffer used for cursor XOR'ing - Max cursor width = Max char width + 1
ColorRGB888 pixel_buffer8[ui::char_width + 1]{};
Color pixel_buffer[ui::char_width + 1]{};
bool mark_change{true};
} cursor_{};
};
+180 -14
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -22,11 +23,12 @@
#include "ui_view_wav.hpp"
#include "ui_fileman.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
using namespace portapack;
#include "string_format.hpp"
namespace ui {
void ViewWavView::update_scale(int32_t new_scale) {
@@ -37,9 +39,24 @@ void ViewWavView::update_scale(int32_t new_scale) {
}
void ViewWavView::refresh_waveform() {
// NB: We can't read from the file to update the waveform when playback is in progress, so defer til playback done.
// (This only happens if the user messes with position or scale fields while playback is occurring)
if (playback_in_progress) {
waveform_update_needed = true;
return;
}
uint8_t bits_per_sample = wav_reader->bits_per_sample();
for (size_t i = 0; i < 240; i++) {
wav_reader->data_seek(position + (i * scale));
wav_reader->read(&waveform_buffer[i], sizeof(int16_t));
if (bits_per_sample == 8) {
uint8_t sample;
wav_reader->read(&sample, 1);
waveform_buffer[i] = (sample - 0x80) * 256;
} else {
wav_reader->read(&waveform_buffer[i], 2);
}
}
waveform.set_dirty();
@@ -72,15 +89,33 @@ void ViewWavView::paint(Painter& painter) {
painter.draw_vline({(Coord)i, 11 * 16}, 8, spectrum_rgb2_lut[amplitude_buffer[i] << 1]);
}
void ViewWavView::on_pos_changed() {
position = (field_pos_seconds.value() * wav_reader->sample_rate()) + field_pos_samples.value();
void ViewWavView::on_pos_time_changed() {
position = (uint64_t)((field_pos_seconds.value() * 1000) + field_pos_milliseconds.value()) * wav_reader->sample_rate() / 1000;
field_pos_milliseconds.set_range(0, ((uint32_t)field_pos_seconds.value() == wav_reader->ms_duration() / 1000) ? wav_reader->ms_duration() % 1000 : 999);
if (!updating_position) {
updating_position = true; // prevent recursion
field_pos_samples.set_value(position);
updating_position = false;
}
refresh_waveform();
}
void ViewWavView::on_pos_sample_changed() {
position = field_pos_samples.value();
if (!updating_position) {
updating_position = true; // prevent recursion
field_pos_seconds.set_value(field_pos_samples.value() / wav_reader->sample_rate());
field_pos_milliseconds.set_value((field_pos_samples.value() * 1000ull / wav_reader->sample_rate()) % 1000);
updating_position = false;
}
refresh_waveform();
}
void ViewWavView::load_wav(std::filesystem::path file_path) {
int16_t sample;
uint32_t average;
wav_file_path = file_path;
text_filename.set(file_path.filename().string());
auto ms_duration = wav_reader->ms_duration();
text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s");
@@ -88,18 +123,27 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
wav_reader->rewind();
text_samplerate.set(to_string_dec_uint(wav_reader->sample_rate()) + "Hz");
text_bits_per_sample.set(to_string_dec_uint(wav_reader->bits_per_sample(), 2));
text_title.set(wav_reader->title());
// Fill amplitude buffer, world's worst downsampling
uint64_t skip = wav_reader->sample_count() / (240 * subsampling_factor);
uint8_t bits_per_sample = wav_reader->bits_per_sample();
for (size_t i = 0; i < 240; i++) {
average = 0;
for (size_t s = 0; s < subsampling_factor; s++) {
wav_reader->data_seek(((i * subsampling_factor) + s) * skip);
wav_reader->read(&sample, 2);
average += (abs(sample) >> 8);
if (bits_per_sample == 8) {
uint8_t sample;
wav_reader->read(&sample, 1);
average += sample / 2;
} else {
int16_t sample;
wav_reader->read(&sample, 2);
average += (abs(sample) >> 8);
}
}
amplitude_buffer[i] = average / subsampling_factor;
@@ -115,11 +159,109 @@ void ViewWavView::reset_controls() {
field_pos_samples.set_value(0);
field_cursor_a.set_value(0);
field_cursor_b.set_value(0);
field_pos_seconds.set_range(0, wav_reader->ms_duration() / 1000);
field_pos_milliseconds.set_range(0, (wav_reader->ms_duration() < 1000) ? wav_reader->ms_duration() % 1000 : 999);
field_pos_samples.set_range(0, wav_reader->sample_count() - 1);
field_scale.set_range(1, std::min(99999ul, wav_reader->sample_count() / 240));
}
bool ViewWavView::is_active() {
return (bool)replay_thread;
}
void ViewWavView::stop() {
if (is_active())
replay_thread.reset();
audio::output::stop();
button_play.set_bitmap(&bitmap_play);
ready_signal = false;
}
void ViewWavView::handle_replay_thread_done(const uint32_t return_code) {
(void)return_code;
stop();
progressbar.set_value(0);
if (return_code == ReplayThread::READ_ERROR)
file_error();
// Playback complete - now it's safe to update waveform view
playback_in_progress = false;
if (waveform_update_needed) {
waveform_update_needed = false;
refresh_waveform();
}
}
void ViewWavView::set_ready() {
ready_signal = true;
}
void ViewWavView::file_error() {
nav_.display_modal("Error", "File read error.");
}
void ViewWavView::start_playback() {
uint32_t sample_rate;
uint8_t bits_per_sample;
auto reader = std::make_unique<WAVFileReader>();
stop();
if (!reader->open(wav_file_path)) {
file_error();
return;
}
playback_in_progress = true;
button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
bits_per_sample = reader->bits_per_sample();
progressbar.set_max(reader->sample_count());
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);
});
baseband::set_audiotx_config(
1536000 / 20, // Rate of sending progress updates
0, // Transmit BW = 0 = not transmitting
0, // Gain - unused
8, // shift_bits_s16, default 8 bits - unused
bits_per_sample, // bits_per_sample
0, // tone key disabled
false, // AM
false, // DSB
false, // USB
false // LSB
);
baseband::set_sample_rate(sample_rate);
transmitter_model.set_sampling_rate(1536000);
audio::output::start();
}
void ViewWavView::on_playback_progress(const uint32_t progress) {
progressbar.set_value(progress);
field_pos_samples.set_value(progress);
}
ViewWavView::ViewWavView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
wav_reader = std::make_unique<WAVFileReader>();
add_children({&labels,
@@ -127,26 +269,33 @@ ViewWavView::ViewWavView(
&text_samplerate,
&text_title,
&text_duration,
&text_bits_per_sample,
&button_open,
&button_play,
&waveform,
&progressbar,
&field_pos_seconds,
&field_pos_milliseconds,
&field_pos_samples,
&field_scale,
&field_cursor_a,
&field_cursor_b,
&text_delta});
&text_delta,
&field_volume});
reset_controls();
button_open.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".WAV");
open_view->on_changed = [this, &nav](std::filesystem::path file_path) {
// Can't show new dialogs in an on_changed handler, so use continuation.
nav.set_on_pop([this, &nav, file_path]() {
if (!wav_reader->open(file_path)) {
nav_.display_modal("Error", "Couldn't open file.");
file_error();
return;
}
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n16-bit mono files.");
if ((wav_reader->channels() != 1) || ((wav_reader->bits_per_sample() != 8) && (wav_reader->bits_per_sample() != 16))) {
nav_.display_modal("Error", "Wrong format.\nWav viewer only accepts\n8 or 16-bit mono files.");
return;
}
load_wav(file_path);
@@ -155,14 +304,26 @@ ViewWavView::ViewWavView(
};
};
field_volume.set_value(field_volume.value());
button_play.on_select = [this, &nav](ImageButton&) {
if (this->is_active())
stop();
else
start_playback();
};
field_scale.on_change = [this](int32_t value) {
update_scale(value);
};
field_pos_seconds.on_change = [this](int32_t) {
on_pos_changed();
on_pos_time_changed();
};
field_pos_milliseconds.on_change = [this](int32_t) {
on_pos_time_changed();
};
field_pos_samples.on_change = [this](int32_t) {
on_pos_changed();
on_pos_sample_changed();
};
field_cursor_a.on_change = [this](int32_t v) {
@@ -179,4 +340,9 @@ void ViewWavView::focus() {
button_open.focus();
}
ViewWavView::~ViewWavView() {
stop();
baseband::shutdown();
}
} /* namespace ui */
+96 -23
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -24,12 +25,15 @@
#include "ui_navigation.hpp"
#include "io_wave.hpp"
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
namespace ui {
class ViewWavView : public View {
public:
ViewWavView(NavigationView& nav);
~ViewWavView();
void focus() override;
void paint(Painter&) override;
@@ -37,15 +41,33 @@ class ViewWavView : public View {
std::string title() const override { return "WAV viewer"; };
private:
app_settings::SettingsManager settings_{
"wav_viewer", app_settings::Mode::NO_RF};
NavigationView& nav_;
static constexpr uint32_t subsampling_factor = 8;
void update_scale(int32_t new_scale);
void refresh_waveform();
void refresh_measurements();
void on_pos_changed();
void on_pos_time_changed();
void on_pos_sample_changed();
void load_wav(std::filesystem::path file_path);
void reset_controls();
bool is_active();
void stop();
void handle_replay_thread_done(const uint32_t return_code);
void set_ready();
void file_error();
void start_playback();
void on_playback_progress(const uint32_t progress);
std::filesystem::path wav_file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool ready_signal{false};
const size_t read_size{2048};
const size_t buffer_count{3};
const uint32_t progress_interval_samples{1536000 / 20};
std::unique_ptr<WAVFileReader> wav_reader{};
@@ -54,32 +76,47 @@ class ViewWavView : public View {
int32_t scale{1};
uint64_t ns_per_pixel{};
uint64_t position{};
bool updating_position{false};
bool playback_in_progress{false};
bool waveform_update_needed{false};
Labels labels{
{{0 * 8, 0 * 16}, "File:", Color::light_grey()},
{{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()},
{{2 * 8, 1 * 16}, "-bit mono", Color::light_grey()},
{{0 * 8, 2 * 16}, "Title:", Color::light_grey()},
{{0 * 8, 3 * 16}, "Duration:", Color::light_grey()},
{{0 * 8, 11 * 16}, "Position: s Scale:", Color::light_grey()},
{{0 * 8, 12 * 16}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 13 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 14 * 16}, "Delta:", Color::light_grey()}};
{{0 * 8, 12 * 16}, "Position: . s Scale:", Color::light_grey()},
{{0 * 8, 13 * 16}, " Sample:", Color::light_grey()},
{{0 * 8, 14 * 16}, "Cursor A:", Color::dark_cyan()},
{{0 * 8, 15 * 16}, "Cursor B:", Color::dark_magenta()},
{{0 * 8, 16 * 16}, "Delta:", Color::light_grey()},
{{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}};
Text text_filename{
{5 * 8, 0 * 16, 12 * 8, 16},
{5 * 8, 0 * 16, 18 * 8, 16},
""};
Text text_samplerate{
{11 * 8, 1 * 16, 8 * 8, 16},
{12 * 8, 1 * 16, 10 * 8, 16},
""};
Text text_title{
{6 * 8, 2 * 16, 18 * 8, 16},
{6 * 8, 2 * 16, 17 * 8, 16},
""};
Text text_duration{
{9 * 8, 3 * 16, 18 * 8, 16},
{9 * 8, 3 * 16, 20 * 8, 16},
""};
Text text_bits_per_sample{
{0 * 8, 1 * 16, 2 * 8, 16},
"16"};
Button button_open{
{24 * 8, 8, 6 * 8, 2 * 16},
"Open"};
ImageButton button_play{
{24 * 8, 17 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Color::green(),
Color::black()};
AudioVolumeField field_volume{
{28 * 8, 18 * 16}};
Waveform waveform{
{0, 5 * 16, 240, 64},
@@ -89,27 +126,40 @@ class ViewWavView : public View {
false,
Color::white()};
ProgressBar progressbar{
{0 * 8, 11 * 16, 30 * 8, 4}};
NumberField field_pos_seconds{
{9 * 8, 11 * 16},
{9 * 8, 12 * 16},
4,
{0, 0},
1,
' ',
true};
NumberField field_pos_milliseconds{
{14 * 8, 12 * 16},
3,
{0, 999},
1,
' '};
'0',
true};
NumberField field_pos_samples{
{14 * 8, 11 * 16},
6,
{0, 999999},
{9 * 8, 13 * 16},
9,
{0, 0},
1,
'0'};
'0',
true};
NumberField field_scale{
{28 * 8, 11 * 16},
2,
{1, 40},
{25 * 8, 12 * 16},
5,
{1, 1},
1,
' '};
' ',
true};
NumberField field_cursor_a{
{9 * 8, 12 * 16},
{9 * 8, 14 * 16},
3,
{0, 239},
1,
@@ -117,7 +167,7 @@ class ViewWavView : public View {
true};
NumberField field_cursor_b{
{9 * 8, 13 * 16},
{9 * 8, 15 * 16},
3,
{0, 239},
1,
@@ -125,8 +175,31 @@ class ViewWavView : public View {
true};
Text text_delta{
{6 * 8, 14 * 16, 30 * 8, 16},
{7 * 8, 16 * 16, 30 * 8, 16},
"-"};
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);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* const p) {
const auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
this->set_ready();
}
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_playback_progress(message.progress);
}};
};
} /* namespace ui */
@@ -0,0 +1,235 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 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_weatherstation.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace ui;
namespace ui {
void WeatherRecentEntryDetailView::update_data() {
// set text elements
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType));
if (entry_.id != WS_NO_ID)
text_id.set("0x" + to_string_hex(entry_.id));
else
text_id.set("-");
if (entry_.temp != WS_NO_TEMPERATURE)
text_temp.set(weather_units_fahr ? to_string_decimal((entry_.temp * 9 / 5) + 32, 1) + STR_DEGREES_F : to_string_decimal(entry_.temp, 2) + STR_DEGREES_C);
else
text_temp.set("-");
if (entry_.humidity != WS_NO_HUMIDITY)
text_hum.set(to_string_dec_uint(entry_.humidity) + "%");
else
text_hum.set("-");
if (entry_.channel != WS_NO_CHANNEL)
text_ch.set(to_string_dec_uint(entry_.channel));
else
text_ch.set("-");
if (entry_.battery_low != WS_NO_BATT)
text_batt.set(to_string_dec_uint(entry_.battery_low) + " " + ((entry_.battery_low == 0) ? "OK" : "LOW"));
else
text_batt.set("-");
text_age.set(to_string_dec_uint(entry_.age) + " sec");
}
WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry)
: nav_{nav},
entry_{entry} {
add_children({&button_done,
&text_type,
&text_id,
&text_temp,
&text_hum,
&text_ch,
&text_batt,
&text_age,
&labels});
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
update_data();
}
void WeatherRecentEntryDetailView::focus() {
button_done.focus();
}
void WeatherView::focus() {
field_frequency.focus();
}
WeatherView::WeatherView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&options_temperature,
&button_clear_list,
&recent_entries_view});
baseband::run_image(portapack::spi_flash::image_tag_weather);
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_step(100000);
options_temperature.on_change = [this](size_t, int32_t i) {
weather_units_fahr = (bool)i;
recent_entries_view.set_dirty();
};
options_temperature.set_selected_index(weather_units_fahr, false);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) {
nav_.push<WeatherRecentEntryDetailView>(entry);
};
baseband::set_subghzd_config(0, receiver_model.sampling_rate()); // 0=am
receiver_model.enable();
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
}
void WeatherView::on_tick_second() {
for (auto& entry : recent) {
entry.inc_age(1);
}
recent_entries_view.set_dirty();
}
void WeatherView::on_data(const WeatherDataMessage* data) {
WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low};
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
// Found within. Move to front of list, increment counter.
(*matching_recent).reset_age();
recent.push_front(*matching_recent);
recent.erase(matching_recent);
} else {
recent.emplace_front(key);
truncate_entries(recent, 64);
}
recent_entries_view.set_dirty();
}
WeatherView::~WeatherView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
}
const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
switch (type) {
case FPW_NexusTH:
return "NexusTH";
case FPW_Acurite592TXR:
return "Acurite592TXR";
case FPW_Acurite606TX:
return "Acurite606TX";
case FPW_Acurite609TX:
return "Acurite609TX";
case FPW_Ambient:
return "Ambient";
case FPW_AuriolAhfl:
return "AuriolAhfl";
case FPW_AuriolTH:
return "AuriolTH";
case FPW_GTWT02:
return "GT-WT02";
case FPW_GTWT03:
return "GT-WT03";
case FPW_INFACTORY:
return "InFactory";
case FPW_LACROSSETX:
return "LaCrosse TX";
case FPW_LACROSSETX141thbv2:
return "LaCrosse TX141THBv2";
case FPW_OREGON2:
return "Oregon2";
case FPW_OREGON3:
return "Oregon3";
case FPW_OREGONv1:
return "OregonV1";
case FPW_THERMOPROTX4:
return "ThermoPro TX4";
case FPW_TX_8300:
return "TX 8300";
case FPW_WENDOX_W6726:
return "Wendox W6726";
case FPW_Acurite986:
return "Acurite986";
case FPW_Invalid:
default:
return "Unknown";
}
}
std::string WeatherView::pad_string_with_spaces(int snakes) {
std::string paddedStr(snakes, ' ');
return paddedStr;
}
template <>
void RecentEntriesTable<ui::WeatherRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
std::string line{};
line.reserve(30);
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType);
if (line.length() < 10) {
line += WeatherView::pad_string_with_spaces(10 - line.length());
} else {
line = truncate(line, 10);
}
std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 1));
std::string humStr = (entry.humidity != WS_NO_HUMIDITY) ? to_string_dec_uint(entry.humidity) + "%" : "-";
std::string chStr = (entry.channel != WS_NO_CHANNEL) ? to_string_dec_uint(entry.channel) : "-";
std::string ageStr = to_string_dec_uint(entry.age);
line += WeatherView::pad_string_with_spaces(6 - temp.length()) + temp;
line += WeatherView::pad_string_with_spaces(5 - humStr.length()) + humStr;
line += WeatherView::pad_string_with_spaces(4 - chStr.length()) + chStr;
line += WeatherView::pad_string_with_spaces(5 - ageStr.length()) + ageStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
} // namespace ui
@@ -0,0 +1,194 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 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.
*/
#ifndef __UI_WEATHER_H__
#define __UI_WEATHER_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/weathertypes.hpp"
using namespace ui;
namespace ui {
static bool weather_units_fahr{false};
struct WeatherRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0x0fffffff; // todo calc the invalid all
uint8_t sensorType = FPW_Invalid;
uint32_t id = WS_NO_ID;
float temp = WS_NO_TEMPERATURE;
uint8_t humidity = WS_NO_HUMIDITY;
uint8_t battery_low = WS_NO_BATT;
uint8_t channel = WS_NO_CHANNEL;
uint16_t age = 0; // updated on each seconds, show how long the signal was last seen
WeatherRecentEntry() {}
WeatherRecentEntry(
uint8_t sensorType,
uint32_t id,
float temp,
uint8_t humidity,
uint8_t channel,
uint8_t battery_low = WS_NO_BATT)
: sensorType{sensorType},
id{id},
temp{temp},
humidity{humidity},
battery_low{battery_low},
channel{channel} {
}
Key key() const {
return (((static_cast<uint64_t>(temp * 10) & 0xFFFF) << 48) ^ static_cast<uint64_t>(id) << 24) |
(static_cast<uint64_t>(sensorType) & 0xFF) << 16 |
(static_cast<uint64_t>(humidity) & 0xFF) << 8 |
(static_cast<uint64_t>(battery_low) & 0xF) << 4 |
(static_cast<uint64_t>(channel) & 0xF);
}
void inc_age(int delta) {
if (UINT16_MAX - delta > age) age += delta;
}
void reset_age() {
age = 0;
}
};
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>;
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>;
class WeatherView : public View {
public:
WeatherView(NavigationView& nav);
~WeatherView();
void focus() override;
std::string title() const override { return "Weather"; };
static const char* getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type);
static std::string pad_string_with_spaces(int snakes);
private:
void on_tick_second();
void on_data(const WeatherDataMessage* data);
NavigationView& nav_;
RxRadioState radio_state_{
433'920'000 /* frequency */,
1'750'000 /* bandwidth */,
2'000'000 /* sampling rate */,
ReceiverModel::Mode::AMAudio};
app_settings::SettingsManager settings_{
"rx_weather",
app_settings::Mode::RX,
{
{"units_fahr"sv, &weather_units_fahr},
}};
WeatherRecentEntries recent{};
OptionsField options_temperature{
{10 * 8, 0 * 16},
2,
{{STR_DEGREES_C, 0},
{STR_DEGREES_F, 1}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
SignalToken signal_token_tick_second{};
Button button_clear_list{
{0, 16, 7 * 8, 32},
"Clear"};
static constexpr auto header_height = 3 * 16;
const RecentEntriesColumns columns{{
{"Type", 10},
{"Temp", 5},
{"Hum", 4},
{"Ch", 3},
{"Age", 4},
}};
WeatherRecentEntriesView recent_entries_view{columns, recent};
MessageHandlerRegistration message_handler_packet{
Message::ID::WeatherData,
[this](Message* const p) {
const auto message = static_cast<const WeatherDataMessage*>(p);
this->on_data(message);
}};
};
class WeatherRecentEntryDetailView : public View {
public:
WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry);
void update_data();
void focus() override;
private:
NavigationView& nav_;
WeatherRecentEntry entry_{};
Text text_type{{10 * 8, 1 * 16, 15 * 8, 16}, "?"};
Text text_id{{10 * 8, 2 * 16, 10 * 8, 16}, "?"};
Text text_temp{{10 * 8, 3 * 16, 8 * 8, 16}, "?"};
Text text_hum{{10 * 8, 4 * 16, 6 * 8, 16}, "?"};
Text text_ch{{10 * 8, 5 * 16, 6 * 8, 16}, "?"};
Text text_batt{{10 * 8, 6 * 16, 6 * 8, 16}, "?"};
Text text_age{{10 * 8, 7 * 16, 10 * 8, 16}, "?"};
Labels labels{
{{0 * 8, 0 * 16}, "Weather Station", Color::light_grey()},
{{0 * 8, 1 * 16}, "Type:", Color::light_grey()},
{{0 * 8, 2 * 16}, "Id: ", Color::light_grey()},
{{0 * 8, 3 * 16}, "Temp:", Color::light_grey()},
{{0 * 8, 4 * 16}, "Humidity:", Color::light_grey()},
{{0 * 8, 5 * 16}, "Channel:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Battery:", Color::light_grey()},
{{0 * 8, 7 * 16}, "Age:", Color::light_grey()},
};
Button button_done{
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
"Done"};
};
} // namespace ui
#endif /*__UI_WEATHER_H__*/
+15 -2
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -198,8 +199,8 @@ void update_audio_mute() {
namespace input {
void start(int8_t alc_mode) {
audio_codec->microphone_enable(alc_mode); // added user-GUI selection for AK4951, ALC mode parameter.
void start(int8_t alc_mode, bool mic_to_HP_enabled) {
audio_codec->microphone_enable(alc_mode, mic_to_HP_enabled); // added user-GUI selection for AK4951, ALC mode parameter. and the check box "Hear Mic"
i2s::i2s0::rx_start();
}
@@ -208,6 +209,14 @@ void stop() {
audio_codec->microphone_disable();
}
void loopback_mic_to_hp_enable() {
audio_codec->microphone_to_HP_enable();
}
void loopback_mic_to_hp_disable() {
audio_codec->microphone_to_HP_disable();
}
} /* namespace input */
namespace headphone {
@@ -232,6 +241,10 @@ uint32_t reg_read(const size_t register_number) {
return audio_codec->reg_read(register_number);
}
void reg_write(const size_t register_number, uint32_t value) {
audio_codec->reg_write(register_number, value);
}
std::string codec_name() {
return audio_codec->name();
}
+11 -2
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -50,12 +51,16 @@ class Codec {
virtual volume_range_t headphone_gain_range() const = 0;
virtual void set_headphone_volume(const volume_t volume) = 0;
virtual void microphone_enable(int8_t alc_mode) = 0; // added user-GUI AK4951 ,selected ALC mode.
virtual void microphone_enable(int8_t alc_mode, bool mic_to_HP_enabled) = 0; // added user-GUI AK4951 ,selected ALC mode.
virtual void microphone_disable() = 0;
virtual void microphone_to_HP_enable() = 0;
virtual void microphone_to_HP_disable() = 0;
virtual size_t reg_count() const = 0;
virtual size_t reg_bits() const = 0;
virtual uint32_t reg_read(const size_t register_number) = 0;
virtual void reg_write(const size_t register_number, const uint32_t value) = 0;
};
namespace output {
@@ -76,9 +81,12 @@ void update_audio_mute();
namespace input {
void start(int8_t alc_mode); // added parameter user-GUI select AK4951-ALC mode for config mic path,(recording mode in datasheet),
void start(int8_t alc_mode, bool mic_to_HP_enabled); // added parameters -GUI select AK4951-ALC mode for config mic path,(recording mode),and the check box "Hear Mic"
void stop();
void loopback_mic_to_hp_enable();
void loopback_mic_to_hp_disable();
} /* namespace input */
namespace headphone {
@@ -101,6 +109,7 @@ namespace debug {
size_t reg_count();
uint32_t reg_read(const size_t register_number);
void reg_write(const size_t register_number, uint32_t value);
std::string codec_name();
size_t reg_bits();
+30 -7
View File
@@ -149,12 +149,18 @@ void set_aprs(const uint32_t baudrate) {
send_message(&message);
}
void set_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) {
void set_btlerx(uint8_t channel_number) {
const BTLERxConfigureMessage message{
baudrate,
word_length,
trigger_value,
trigger_word};
channel_number};
send_message(&message);
}
void set_btletx(uint8_t channel_number, char* macAddress, char* advertisementData, uint8_t pduType) {
const BTLETxConfigureMessage message{
channel_number,
macAddress,
advertisementData,
pduType};
send_message(&message);
}
@@ -167,6 +173,17 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
send_message(&message);
}
void set_fsk(const size_t deviation) {
const FSKRxConfigureMessage message{
taps_200k_decim_0,
taps_16k0_decim_1,
taps_11k0_channel,
2,
deviation};
send_message(&message);
}
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count) {
const AFSKTxConfigureMessage message{
afsk_samples_per_bit,
@@ -194,6 +211,7 @@ void set_audiotx_config(
const float deviation_hz,
const float audio_gain,
uint8_t audio_shift_bits_s16,
uint8_t bits_per_sample,
const uint32_t tone_key_delta,
const bool am_enabled,
const bool dsb_enabled,
@@ -204,6 +222,7 @@ void set_audiotx_config(
deviation_hz,
audio_gain,
audio_shift_bits_s16,
bits_per_sample,
tone_key_delta,
(float)persistent_memory::tone_mix() / 100.0f,
am_enabled,
@@ -261,8 +280,7 @@ void set_pocsag() {
}
void set_adsb() {
const ADSBConfigureMessage message{
1};
const ADSBConfigureMessage message{};
send_message(&message);
}
@@ -303,6 +321,11 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
send_message(&message);
}
void set_subghzd_config(uint8_t modulation = 0, uint32_t sampling_rate = 0) {
const SubGhzFPRxConfigureMessage message{modulation, sampling_rate};
send_message(&message);
}
static bool baseband_image_running = false;
void run_image(const spi_flash::image_tag_t image_tag) {
+5 -2
View File
@@ -63,15 +63,17 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone();
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
void set_fifo_data(const int8_t* data);
void set_pitch_rssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
void kill_afsk();
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
void set_fsk(const size_t deviation);
void set_aprs(const uint32_t baudrate);
void set_btle(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
void set_btlerx(uint8_t channel_number);
void set_btletx(uint8_t channel_number, char* macAddress, char* advertisementData, uint8_t pduType);
void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
@@ -86,6 +88,7 @@ void set_spectrum(const size_t sampling_rate, const size_t trigger);
void set_siggen_tone(const uint32_t tone);
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
void request_beep();
void run_image(const portapack::spi_flash::image_tag_t image_tag);
+128 -14
View File
@@ -1169,20 +1169,20 @@ static constexpr uint8_t bitmap_icon_tpms_data[] = {
0xEC,
0x37,
0x36,
0x6C,
0x1A,
0x58,
0x0B,
0xD0,
0x0B,
0xD0,
0x0B,
0xD0,
0x0B,
0xD0,
0x1A,
0x58,
0x36,
0x6D,
0x3A,
0x59,
0x4B,
0xD5,
0x8B,
0xD3,
0xCB,
0xD1,
0xAB,
0xD2,
0x9A,
0x5C,
0xB6,
0x6C,
0xEC,
0x37,
@@ -5683,6 +5683,120 @@ static constexpr Bitmap bitmap_icon_trim{
{16, 16},
bitmap_icon_trim_data};
static constexpr uint8_t bitmap_icon_hide_data[] = {
0x00,
0x00,
0x00,
0x40,
0x00,
0x20,
0xE0,
0x17,
0x18,
0x18,
0xC4,
0x27,
0x62,
0x42,
0x21,
0x85,
0xA1,
0x84,
0x62,
0x46,
0xA4,
0x23,
0x18,
0x18,
0xE8,
0x07,
0x04,
0x00,
0x02,
0x00,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_hide{
{16, 16},
bitmap_icon_hide_data};
static constexpr uint8_t bitmap_icon_thermometer_data[] = {
0xC0,
0x00,
0x20,
0x01,
0x10,
0x02,
0x10,
0x3A,
0x10,
0x02,
0x10,
0x1A,
0x10,
0x02,
0xD0,
0x3A,
0xD0,
0x02,
0xD0,
0x1A,
0xD0,
0x02,
0xE8,
0x05,
0xE8,
0x05,
0xC8,
0x04,
0x10,
0x02,
0xE0,
0x01,
};
static constexpr Bitmap bitmap_icon_thermometer{
{16, 16},
bitmap_icon_thermometer_data};
static constexpr uint8_t bitmap_icon_brightness_data[] = {
0x00,
0x00,
0x80,
0x01,
0x80,
0x01,
0x08,
0x10,
0xC0,
0x03,
0xE0,
0x07,
0xF0,
0x0F,
0xF6,
0x6F,
0xF6,
0x6F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x08,
0x10,
0x80,
0x01,
0x80,
0x01,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_brightness{
{16, 16},
bitmap_icon_brightness_data};
} /* namespace ui */
#endif /*__BITMAP_HPP__*/
+46 -19
View File
@@ -217,6 +217,27 @@ ClockManager::Reference ClockManager::get_reference() const {
return reference;
}
std::string ClockManager::get_source() {
std::string source_name("---");
switch (reference.source) {
case ClockManager::ReferenceSource::Xtal:
source_name = "HackRF";
break;
case ClockManager::ReferenceSource::PortaPack:
source_name = "PortaPack";
break;
case ClockManager::ReferenceSource::External:
source_name = "External";
break;
}
return source_name;
}
std::string ClockManager::get_freq() {
return to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
}
static void portapack_tcxo_enable() {
portapack::io.reference_oscillator(true);
@@ -561,26 +582,32 @@ void ClockManager::stop_audio_pll() {
}
void ClockManager::enable_clock_output(bool enable) {
if (enable) {
clock_generator.enable_output(clock_generator_output_og_clkout);
if (portapack::persistent_memory::clkout_freq() < 1000) {
clock_generator.set_ms_frequency(clock_generator_output_og_clkout, portapack::persistent_memory::clkout_freq() * 128000, si5351_vco_f, 7);
} else {
clock_generator.set_ms_frequency(clock_generator_output_og_clkout, portapack::persistent_memory::clkout_freq() * 1000, si5351_vco_f, 0);
}
} else {
clock_generator.disable_output(clock_generator_output_og_clkout);
if (hackrf_r9) {
gpio_r9_clkout_en.output();
gpio_r9_clkout_en.write(enable);
// NOTE: RETURNING HERE IF HACKRF_R9 TO PREVENT CLK2 FROM BEING DISABLED OR FREQ MODIFIED SINCE CLK2 ON R9 IS
// USED FOR BOTH CLKOUT AND FOR THE MCU_CLOCK (== GP_CLKIN) WHICH OTHER LP43XX CLOCKS CURRENTLY RELY ON.
// FUTURE TBD: REMOVE OTHER LP43XX CLOCK DEPENDENCIES ON GP_CLKIN, THEN DELETE THE return LINE BELOW TO ALLOW
// CLKOUT FREQ CHANGES ON R9 BOARDS.
return;
}
auto si5351_clock_control_common = hackrf_r9
? si5351a_clock_control_common
: si5351c_clock_control_common;
const auto ref_pll = hackrf_r9
? ClockControl::MultiSynthSource::PLLA
: get_si5351c_reference_clock_generator_pll(reference.source);
auto clkout_select = hackrf_r9 ? clock_generator_output_r9_clkout : clock_generator_output_og_clkout;
if (enable)
clock_generator.set_clock_control(clock_generator_output_og_clkout, si5351_clock_control_common[clock_generator_output_og_clkout].ms_src(ref_pll).clk_pdn(ClockControl::ClockPowerDown::Power_On));
else
clock_generator.set_clock_control(clock_generator_output_og_clkout, ClockControl::power_off());
if (enable) {
clock_generator.enable_output(clkout_select);
if (portapack::persistent_memory::clkout_freq() < 1000) {
clock_generator.set_ms_frequency(clkout_select, portapack::persistent_memory::clkout_freq() * 128000, si5351_vco_f, 7);
} else {
clock_generator.set_ms_frequency(clkout_select, portapack::persistent_memory::clkout_freq() * 1000, si5351_vco_f, 0);
}
auto si5351_clock_control_common = hackrf_r9 ? si5351a_clock_control_common : si5351c_clock_control_common;
const auto ref_pll = hackrf_r9 ? ClockControl::MultiSynthSource::PLLA : get_si5351c_reference_clock_generator_pll(reference.source);
clock_generator.set_clock_control(clkout_select, si5351_clock_control_common[clkout_select].ms_src(ref_pll).clk_pdn(ClockControl::ClockPowerDown::Power_On));
} else {
clock_generator.disable_output(clkout_select);
clock_generator.set_clock_control(clkout_select, ClockControl::power_off());
}
}
+3
View File
@@ -22,6 +22,7 @@
#ifndef __CLOCK_MANAGER_H__
#define __CLOCK_MANAGER_H__
#include "string_format.hpp"
#include "ch.h"
#include "hal.h"
@@ -74,6 +75,8 @@ class ClockManager {
uint32_t get_frequency_monitor_measurement_in_hertz();
Reference get_reference() const;
std::string get_source();
std::string get_freq();
void enable_clock_output(bool enable);
+11 -3
View File
@@ -27,15 +27,23 @@
void config_mode_blink_until_dfu();
void config_mode_set() {
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_GUARD_VALUE);
uint32_t cms = portapack::persistent_memory::config_mode_storage_direct();
if ((cms >= CONFIG_MODE_GUARD_VALUE) && (cms < CONFIG_MODE_LIMIT_VALUE))
cms += 1;
else
cms = CONFIG_MODE_GUARD_VALUE;
portapack::persistent_memory::set_config_mode_storage_direct(cms);
}
bool config_mode_should_enter() {
return portapack::persistent_memory::config_mode_storage() == CONFIG_MODE_GUARD_VALUE;
if (portapack::persistent_memory::config_disable_config_mode_direct())
return false;
else
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_LIMIT_VALUE;
}
void config_mode_clear() {
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_NORMAL_VALUE);
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_NORMAL_VALUE);
}
uint32_t blink_patterns[] = {
+9
View File
@@ -28,9 +28,18 @@
#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
// number of boot failures before entering config menu mode
#define BOOT_FAILURES_BEFORE_CONFIG_MODE 3
#define CONFIG_MODE_GUARD_VALUE 0xbadb0000
#define CONFIG_MODE_LIMIT_VALUE (CONFIG_MODE_GUARD_VALUE + BOOT_FAILURES_BEFORE_CONFIG_MODE - 1)
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
void config_mode_set();
bool config_mode_should_enter();
void config_mode_clear();
void config_mode_enable(bool v);
bool config_mode_disabled();
void config_mode_run();
+6 -7
View File
@@ -25,14 +25,12 @@
#include "file.hpp"
#include <cstring>
namespace std {
int database::retrieve_mid_record(MidDBRecord* record, std::string search_term) {
file_path = "AIS/mids.db";
index_item_length = 4;
record_length = 32;
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
@@ -42,7 +40,7 @@ int database::retrieve_airline_record(AirlinesDBRecord* record, std::string sear
index_item_length = 4;
record_length = 64;
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
@@ -52,12 +50,15 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
index_item_length = 7;
record_length = 146;
result = std::database::retrieve_record(file_path, index_item_length, record_length, record, search_term);
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
int database::retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term) {
if (search_term.empty())
return DATABASE_RECORD_NOT_FOUND;
auto result = db_file.open(file_path);
if (!result.is_valid()) {
number_of_records = (db_file.size() / (index_item_length + record_length)); // determine number of records in file
@@ -91,5 +92,3 @@ int database::retrieve_record(std::string file_path, int index_item_length, int
} else
return (DATABASE_NOT_FOUND);
}
} /* namespace std */
+3 -5
View File
@@ -30,7 +30,6 @@
#include "file.hpp"
namespace std {
class database {
public:
#define DATABASE_RECORD_FOUND 0 // record found in database
@@ -62,9 +61,9 @@ class database {
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
private:
string file_path = ""; // path inclusing filename
int index_item_length = 0; // length of index item
int record_length = 0; // length of record
std::string file_path = ""; // path inclusing filename
int index_item_length = 0; // length of index item
int record_length = 0; // length of record
File db_file{};
int number_of_records = 0;
@@ -77,6 +76,5 @@ class database {
int retrieve_record(std::string file_path, int index_item_length, int record_length, void* record, std::string search_term);
};
} // namespace std
#endif /*__DATABASE_H__*/
+1
View File
@@ -21,6 +21,7 @@
*/
#include <string>
#include <cstdint>
#ifndef __DE_BRUIJN_H__
#define __DE_BRUIJN_H__
+20 -14
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2023 Bernd Herzog
* Copyright (C) 2023 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -258,38 +259,43 @@ void draw_stack_dump() {
}
}
// Disk I/O in this function doesn't work after a fault
// Using the stack while dumping the stack isn't ideal, but hopefully anything imporant is still on the call stack.
bool stack_dump() {
uint32_t num_words = &__process_stack_end__ - &__process_stack_base__;
return memory_dump(&__process_stack_base__, num_words, true);
}
bool memory_dump(uint32_t* addr_start, uint32_t num_words, bool stack_flag) {
Painter painter;
std::string debug_dir = "DEBUG";
std::filesystem::path filename{};
File stack_dump_file{};
File dump_file{};
bool error;
std::string str;
uint32_t* addr_end;
uint32_t* p;
int n{0};
bool data_found{false};
make_new_directory(debug_dir);
filename = next_filename_matching_pattern(debug_dir + "/STACK_DUMP_????.TXT");
ensure_directory(debug_dir);
filename = next_filename_matching_pattern(debug_dir + "/" + (stack_flag ? "STACK" : "MEMORY") + "_DUMP_????.TXT");
error = filename.empty();
if (!error)
error = stack_dump_file.create(filename) != 0;
error = dump_file.create(filename) != 0;
if (error) {
painter.draw_string({16, 320 - 32}, ui::Styles::red, "ERROR DUMPING " + filename.filename().string() + "!");
return false;
}
for (p = &__process_stack_base__; p < &__process_stack_end__; p++) {
addr_end = addr_start + num_words;
for (p = addr_start; p < addr_end; p++) {
// skip past unused stack words
if (!data_found) {
if (stack_flag && !data_found) {
if (*p == CRT0_STACKS_FILL_PATTERN)
continue;
else {
data_found = true;
auto stack_space_left = p - &__process_stack_base__;
stack_dump_file.write_line(to_string_hex((uint32_t)&__process_stack_base__, 8) + ": Unused words " + to_string_dec_uint(stack_space_left));
auto stack_space_left = p - addr_start;
dump_file.write_line(to_string_hex((uint32_t)addr_start, 8) + ": Unused words " + to_string_dec_uint(stack_space_left));
// align subsequent lines to start on 16-byte boundaries
p -= (stack_space_left & 3);
@@ -299,20 +305,20 @@ bool stack_dump() {
// write address
if (n++ == 0) {
str = to_string_hex((uint32_t)p, 8) + ":";
stack_dump_file.write(str.data(), 9);
dump_file.write(str.data(), 9);
}
// write stack dword
str = " " + to_string_hex(*p, 8);
stack_dump_file.write(str.data(), 9);
dump_file.write(str.data(), 9);
if (n == 4) {
stack_dump_file.write("\r\n", 2);
dump_file.write("\r\n", 2);
n = 0;
}
}
painter.draw_string({16, 320 - 32}, ui::Styles::green, filename.filename().string() + " dumped!");
painter.draw_string({0, 320 - 16}, ui::Styles::green, filename.filename().string() + " dumped!");
return true;
}

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