Compare commits

...

325 Commits

Author SHA1 Message Date
gullradriel c4f32ac436 update submodule (#3277) 2026-07-25 18:54:33 +02:00
Frederic BORRY efe214167d Added sn0ren's SPECTRUM BMP files (#3276) 2026-07-24 18:37:55 +08:00
Copilot 483a100100 Fix ADSB altitude integer overflow and add on-ground indicator (#3275)
* Initial plan

* Fix ADSB altitude integer overflow and add on-ground indicator

- Fix display overflow: use to_string_dec_int (signed) instead of
  to_string_dec_uint (unsigned) for altitude display - per ICAO Annex 10
  Vol IV 3.1.2.6.5.4, altitude = 25N-1000 can be as low as -1000ft
- Remove incorrect altitude clamp (altitude < 0 => 0) in DF 0/4/20 decoder
- Add on_ground flag to AircraftRecentEntry; set from TC 5-8 (surface
  position) messages, cleared on TC 9-22 (airborne position) messages
- Display 'GND' in altitude column when aircraft is on ground
- Clear altitude to 0 when on_ground is set to prevent stale data in map
  color calculation

Closes #3274

* Revert on-ground indicator; keep only the overflow and clamp fixes

Remove on_ground field, TC 5-8 handling, and "GND" display per review
feedback. Only keep the two minimal bug fixes:
- Signed altitude display (to_string_dec_int vs to_string_dec_uint)
- Remove erroneous altitude < 0 clamp; update comment to show the math
2026-07-23 01:16:21 +02:00
Pezsma 8561ff32b3 Gpio modify v4 (#3266)
* Refactor GPIO handling for RFFC5072: update pin mappings and simplify initialization

* Refactor GPIO handling: replace portapack::gpio_dfu with dfu_button and clean up unused GPIOs

* Refactor MAX283x GPIO handling: update pin mappings and simplify initialization

* Refactor GPIO handling: update MAX2831 shutdown pin configuration

* Refactor GPIO handling: add MAX2831 RXHP and RXTX control, update related configurations

* Refactor MAX2831 frequency setting to include reference divider parameter and improve validation checks

* Refactor MAX2831 frequency setting to remove reference divider parameter and improve frequency validation

* copilot
2026-07-10 21:37:41 +02:00
gullradriel 87eb8e5863 update submodule (#3267) 2026-07-09 23:11:39 +02:00
Frederic BORRY d11669f711 EPIRB TX SGB support (#3263)
* Update BEACONS.TXT
Added test beacon with 3 bch1 errors and 2 bch2 errors to test multiple bit error correction.
* Fist step to SGB format
* Added SGB to manual mode.
* SGB support
- Added SGB frame to beacons file
- Fixed hex display for SGB
- Fixed frame type option display
- Fixed self-test mode activation logic
- Fixed PRN for self-test mode
2026-07-09 10:36:26 +02:00
Totoo 7513cd46fc Pause on the looking glass (#3261) 2026-07-09 09:43:45 +02:00
Totoo d671455f79 Added tetra rx ext app (#3257) 2026-07-09 09:10:08 +02:00
Totoo 42122a739c remove const temp measurement from core to the ext app. we don't need it (#3259) 2026-07-08 10:09:53 +08:00
未来方舟 03fe508aae fix looking glass frequency cast error (#3253)
* fix looking glass frequency cast error

* add useless protecter back
2026-07-08 10:08:24 +08:00
未来方舟 e3eafbeb66 Add benchmark function for mbt (#3255) 2026-07-07 11:32:00 +02:00
未来方舟 e3b3fa2fc8 fine tune app search ui (#3256) 2026-07-07 11:06:31 +02:00
未来方舟 5f0ae22f66 remove my dumb nicknames in files (#3254) 2026-07-07 11:04:34 +02:00
Totoo f9dd28f31d fix dfu button (#3252) 2026-07-07 00:31:44 +02:00
Matej Sochan b99cec5111 Feature/signal hunter (#3245) 2026-07-06 11:05:49 +08:00
Pezsma fe2ba80a10 refactor: simplify audio volume handling in AudioVolumeField (#3246) 2026-07-04 08:53:28 +02:00
Luca b5d84fcc4d Airband VOR receiver and trasmitter (#3244)
* First test

* Reorder includes for consistency and fix string dereference in VorCdiIndicator

* Add memory section and linker script entry for vor_rx application

* Testing VOR TX

* Moving VOR TX to SD

* Code refactoring to align with guidelines

* Add TODO comment to verify radial sign convention in vor_tx_config

* Remove unnecessary set_dirty() calls in VorRxView methods

* Update Course Deviation Indicator label and adjust UI element positions

* Update VorTxView to run prepared image from memory map

* Update build condition in nightly release workflow to include workflow_dispatch event

* Reverting action changes

* Adjust Course Deviation Indicator layout and refine painting logic

* Refactor VorRxView UI elements for improved layout and clarity

* Add calibration field and update radial calculation logic in VorRxView

* Enhance VOR processing: add renormalization to phase oscillator, improve signal handling, and update configuration parameters

* Implement radial smoothing and TO/FROM state handling in VorRxView; update VOR processing logic

* Setting comments inline

* Refactor VorCdiIndicator and VorRxView: change normalize_signed_degrees to use int32_t, update radial smoothing to use fixed point arithmetic to reduce flash usage

* Update external app address end to 0xAE0B0000

* Update set_vor_tx_config to include enabled parameter for consistency

* Refactor VorRx to omit decim_2 stage and update comments for clarity on VOR processing

* Add RSSI, Channel, and Audio fields to VorRxView to mimic existing apps

* Refactor VorRxView labels and status messages

* Update VorRxView calibration field range to allow full circle input

* Completing Ident transmission logic

* Fixing TX gain and moving log label

* Refactor VOR phase calculations and update radial offset handling for accurate bearing representation

* Add low-pass filters to strip 9960 Hz subcarrier from audio output
2026-07-04 08:49:21 +02:00
Sandbox 7481aefe9f customizable name attack option "CUSTOM" for the BLESPAM App (#3239) 2026-07-02 16:14:17 +02:00
未来方舟 6b641c8a5d You can search app now (#3241) 2026-07-02 10:19:53 +02:00
Pezsma 2ad34bbc09 Gpio modify v3 (#3238)
* Refactor GPIO mappings and definitions for improved clarity and functionality

- Updated SCU_ARRAY_SIZE definitions in pal_lld.h for PRALINE and non-PRALINE configurations.
- Modified pin mappings in gpio.hpp to reflect new hardware configurations, including additional control pins for RF and audio components.
- Removed obsolete GPIO definitions from hackrf_gpio.hpp and streamlined the code for better maintainability.
- Added new GPIO definitions for mix bypass, amplifier control, and other RF-related functionalities to enhance the system's capabilities.

* Refactor GPIO mappings and update SCU_ARRAY_SIZE for LPC43xx platform

- Updated SCU_ARRAY_SIZE to 80 for PRALINE configuration and 58 for others.
- Added new GPIO mappings for auxiliary power control, antenna bias, and R9 clock enable signals in gpio.hpp.
- Removed commented-out PPS output mapping in hackrf_gpio.hpp.
- Adjusted GPIO definitions for R9 clock signals to ensure proper functionality.

* Refactor RF path initialization and configuration for improved clarity and functionality

* Refactor RF path configuration and GPIO mappings for improved clarity and functionality

* Potential fix for pull request finding

* comment

* aux power polarrity

* Fix formatting of pin_aux_power_enable declaration
2026-07-02 10:05:34 +02:00
Totoo 9c74a1f392 fix mbt build on rare errors (#3242) 2026-07-01 14:42:25 +02:00
gullradriel abadf35e06 Lna investigations and fix (#3240)
* The LNA gain field 'L' lives in LPF_VGA_2 (reg 6), not RXRF_2 (reg 2). RXRF_2 only holds LNAgain_SPI_EN. The configure_rx_gain() function wrote 'L' into lpf_vga_2 but marked RXRF_2 dirty => the LNA value only got flushed incidentally because the following VGA write marks LPF_VGA_2 dirty. Drop the stray RXRF_2 dirty flag and rely on the LPF_VGA_2 flag, which carries both L and VGA.

* The baseband build defined IS_H1_R9 as the compile-time constant 0, on the (now incorrect) assumption that detected_platform() isn't linked. The sd_over_usb image is the only baseband target that compiles the IS_H1_R9-gated upstream files (rf_path.c, si5351c.c, sgpio.c, platform_gpio.c, platform_scu.c), and it *does* link platform_detect.c and call detect_hardware_platform() at startup. Forcing IS_H1_R9=0 dead-stripped every 'if (IS_H1_R9)' branch, so a real HackRF One r9 was set up with the OG clock/GPIO/RF-path config in that image.
Define IS_H1_R9 as the runtime '(detected_platform()==BOARD_ID_HACKRF1_R9)', mirroring upstream platform-detect.cmake for HACKRF_ONE. All usages are '#ifdef IS_H1_R9' + 'if (IS_H1_R9)' (no arithmetic '#if'), so a function-call expression is safe, single-quoted -D with no internal spaces keeps it intact through the shell-split DDEFS blob.

---------

Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-07-01 08:47:07 +02:00
Totoo 288e87537a MAX 17055 sleep bug fixes. 2026-06-25 09:08:44 +02:00
gullradriel d25899de10 Upgrade subm plus warning fix (#3235) 2026-06-25 08:46:36 +02:00
Totoo 8a16ec829c fix clock source change on praline (#3233)
* fix clock source change on praline

* whoops
2026-06-22 16:50:16 +02:00
Totoo f6d6e3be4c Fix battery display bug, and the flash time (#3232)
* battfix

* por again, but nicer
2026-06-22 16:20:49 +02:00
Pezsma c16bac24c7 Add RSSI input configuration for VGA output measurement (#3231) 2026-06-21 11:17:09 +02:00
Payalo64 4e6a2cc934 Add 3.3GHz Analog FPV Channels to FPV_ANALOG.TXT (#3230) 2026-06-20 18:28:02 +02:00
Pezsma cafe62564e Gpio modify (#3229)
* Refactor GPIO configuration for PRALINE and non-PRALINE setups

- Updated PinMap structure to include gpio_mode for better flexibility.
- Added new GPIO mappings for SGPIO pins with appropriate configurations.
- Commented out unused GPIO definitions in hackrf_gpio.hpp to improve code clarity.
- Adjusted GPIO initialization for control pins to utilize the new PinMap structure.
- Ensured compatibility for both PRALINE and non-PRALINE configurations by using preprocessor directives.

* Refactor GPIO handling and remove LED abstraction

- Updated GPIO class to support logical polarity, enabling/disabling features based on their configured state.
- Replaced direct GPIO manipulation in power control functions with new GPIO methods for better readability and maintainability.
- Removed the LED class and its associated functionality, as it was deemed unnecessary for the current implementation.
- Adjusted GPIO initialization for various components, ensuring correct polarity settings for VAA and power enable pins.
- Cleaned up unused includes and commented-out code in hackrf_gpio.hpp.

* Refactor GPIO LED control methods to use setActive() and setInactive() for improved clarity

* Refactor GPIO control methods to use setActive() and setInactive() for improved clarity and consistency

* copilot

* Update GPIO control logic and pin definitions for clarity and consistency

* Refactor GPIO methods for improved naming consistency and clarity
2026-06-19 07:48:55 +02:00
gullradriel 960e5b8329 Upgrade subm plus warning fix (#3227)
* fix compilation warning
* update submodule
2026-06-17 11:34:00 +02:00
Totoo f5a5e8e5e8 Map improv. (#3226) 2026-06-16 17:54:32 +02:00
Synray d8a1e2530a Add Security+ 2.0 TX App (#3217)
* Add U64 BoundSetting
* Add secplus-tx
* Add secplus license
* Update icon
* UI update
* Add filesystem::path BoundSetting
* Add file UI
* Rename file
* Add save button
* Fix warnings, add integer conversions to UI macros
* UI update
* Copilot suggestions, name edit UI
* Fix buffer length
* Style update
* Avoid recursion in Widget::style()
* Apply suggestions from code review
* Move secplustx.cpp to external
* Shorten title
* Space optimizations, remove FilePath setting
* Remove assertions
* Change namespace
* Change title to match
2026-06-15 19:28:47 +02:00
Totoo b84a424875 MBT update 2026-06-13 09:50:25 +08:00
gullradriel 62d0e598d4 Update submodule (#3222)
* update submodule to latest GSG modifications
* build: fix delay function
* update submodule to include praline fix
* fix dep to rebuild/reinclude hackrf_usb_ram.bin
2026-06-12 22:15:44 +02:00
Synray a93f2ff583 Remove sine table lookup from proc_ook (#3201) 2026-06-12 16:26:13 +02:00
Totoo e2586ceb22 proper fix for flasher (#3223) 2026-06-12 16:25:43 +02:00
未来方舟 5a2b0cdc2a build tool and agent.md (#2993)
* init

* note

* detect arch

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

* fix typo

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

* Update mbt

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

* protect rm -rf

* remove wrong ai code

* Update mbt

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

* add agent file

* remove empty file

* agent.md rewrite

* agent.md rewrite

* fix on ubuntu

* err check

* add make

* add python3-yaml

* add binutils

* fix the build order to avoid some compile bugs

* mg

* remove trash

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
Co-authored-by: HTotoo <ttotoo@gmail.com>
2026-06-12 16:52:13 +08:00
Totoo f852900cd0 Epirbrx sizefix (#3221) 2026-06-11 17:18:05 +02:00
Pezsma 9ab367f2e0 Add praline gpio (#3218)
* Implement antenna bias control for Praline and update GPIO configurations
* Add GPIO power management functions and update board initialization for Praline
* Refactor power management functions for Praline and update GPIO configurations
* Update GPIO configurations for Praline and enable power control pins. AA_EN enabled
* Refactor GPIO configuration for Praline: update array size definitions and clean up comments
* Add P1 and P2 control functions for PRALINE
* Introduced P1_Function and P2_Function enums in ClockManager to manage multiplexer control.
* mplemented set_p1_control and set_p2_control methods for configuring P1 and P2 control pins based on specified functions.
* Updated clock_manager.hpp and clock_manager.cpp to include new functionality.
* Modified MAX2837 and MAX2839 initialization to conditionally configure GPIO based on PRALINE.
* Adjusted RFFC507x and RFFC507x_SPI initialization to include GPIO setup for PRALINE.
- Refactored RF path initialization to remove unnecessary GPIO setup.
- Updated board configuration for PRALINE to reflect new GPIO settings and pin configurations.
- Cleaned up FPGA bridge code by removing unused pin configuration functions.
- Adjusted SCU array size in pal_lld.h for PRALINE.
- Enhanced hackrf_gpio.hpp to define multiplexer control pins for PRALINE.
- Modified LED setup to accommodate active-low configuration for PRALINE.

* Refactor GPIO and power control functions

- Removed unused power control function declarations from board.h.
- Enhanced gpio.hpp with new pin setup functions and GPIO control structures for PRALINE.
- Consolidated multiplexer control pin definitions into gpio_control namespace.
- Moved power control function implementations to gpio.cpp, including detailed VAA power management logic.
- Updated power control functions to handle both PRALINE and HackRF R9 configurations.
- Cleaned up hackrf_gpio.hpp by removing redundant multiplexer control definitions.

* Refactor GPIO control for PRALINE: update MAX2831 and RFFC507x configurations, add new GPIO mappings

* Refactor GPIO configurations for PRALINE: update anti-aliasing filter pin mapping, enhance LED setup logic, and add placeholder GPIO entries.

* Refactor GPIO configurations for PRALINE: update LED mappings and remove unused anti-aliasing filter GPIO entry.
2026-06-11 13:24:42 +02:00
Arne Luehrs c7a0846645 EPIRB RX: add receiver-overload indicator on channel bar (#3212)
* EPIRB RX: add receiver-overload indicator on channel bar

Tint the channel-power bar red when the front-end overloads (ADC near
full scale). EPIRB demodulation relies on the constant-envelope carrier
and the +/-1.1 rad biphase phase jumps; ADC clipping distorts both, so a
visible cue to reduce RF-amp/LNA/VGA gain is useful.

Implemented in the shared ui::Channel widget via an opt-in
set_overload_threshold(): when the last ChannelStatistics max_db reaches
the threshold the bar is drawn red instead of blue. Default threshold is
above the 0 dBFS ceiling, so all existing users are unaffected. The
EPIRB view enables it at -3 dBFS.

Done in the widget (main firmware) rather than the EPIRB app because the
epirb_rx external app is already at the 32 KB flash limit; the app only
pays for a single setter call.
2026-06-11 11:10:23 +02:00
Arne Luehrs 569bcaad22 Epirb afc wide capture (#3213)
* EPIRB RX: widen AFC capture range to +/-5 kHz

The AFC estimate was only applied after carrier lock, so carrier
acquisition ran on raw phase deltas and could only tolerate offsets of
a few hundred Hz before the 0.6/0.7/1.6 rad detection thresholds (on
the 12-sample accumulator) broke down.

Track the carrier offset continuously in the IDLE state with a
first-order loop (AFC_TRACK_ALPHA), so the de-biased accumulator
self-centers for any offset within the discriminator Nyquist (~24 kHz)
*before* the thresholds run. ALPHA = 0.005 pulls a +/-5 kHz offset
under the 0.6 rad lock threshold in ~11 ms, well inside the 160 ms
preamble / 80 ms stability window. The IDLE rise-detect threshold is
also made symmetric (fabsf) now that the bias is removed.

Also add the missing <cstdint> include to test_convert.cpp so the
application_test suite compiles under the current toolchain.

Verified: baseband_epirb_rx.elf builds (flash 53%, RAM 12%) and
baseband_test passes.
2026-06-11 11:01:54 +02:00
Totoo 6b5955e0b5 Enhance battery detection and configuration handling (#3197)
* indicate batt changed event
* added sleep config, and use it.
* reinit if new batt detected.
* more detect
* repl batt setting
* disabled some things
* copilot fixes
2026-06-11 10:52:20 +02:00
Synray 9042d1c433 Use UI_ macros in looking glass (#3208) 2026-06-09 20:46:35 +02:00
未来方舟 378b2808ba Looking Glass frequency list now supports muiti file loads (#3215) 2026-06-08 15:22:29 +02:00
Erwin Ried 5b363b79aa Update links and images in README.md 2026-05-31 22:56:20 +02:00
Brumi-2021 0d51787099 Clean_gcc_14_2_1_compiler_errors (#3206)
* Clean_gcc_14_2_1_compiler_errors

* format issues
2026-05-31 17:34:30 +02:00
gullradriel 8f5b25b10d PRALINE Tx and clock fix (Pr 3192 takeover (#3198) )
* fix clock init and add mayhem tx capacity
* aligned external clock and RX config
* fix(clock): cap TX interpolation to x16 to prevent doubled waveform speed

  The TX gateware only implements interpolation factors x1..x16 (tx_intrp
  0..4), but the rate search could select n==5 (x32) for very low TX
  sample rates. That left the AFE/CLK0/CLK1 running at the x32 rate while
  the FPGA only interpolated by x16, clocking the TX datapath at twice the
  gateware output rate. Cap afe_rate and _resampling_n to the x16 limit in
  TX so the clocks stay aligned with the interpolation setting.

* fix(praline): source PLL1 from 40MHz GP_CLKIN instead of 12MHz XTAL

  For the intermediate 90-110MHz PLL1 stepping stage, source PLL1 from
  GP_CLKIN (Si5351 CLK2/MCU_CLK at 40MHz) giving N=2, M=10, Fclk=100MHz,
  rather than the on-chip 12MHz XTAL bootstrap path. This matches the
  non-PRALINE path. Remove the dead commented-out XTAL enable/PLL code.

* docs(fpga_bridge): correct register 0x03 description for PRALINE gateware

  Register 0x03 is RX_DIGITAL_GAIN in RX and TX_NCO_CTRL in TX, not
  RX_PSTEP.

* Fix fpga_tx_set_nco_enable to use FPGA_REG3_TX_NCO_CTRL (0x03)
2026-05-31 12:44:40 +02:00
Synray 4e56d0f420 Add RSSI toggle button to looking glass (#3204) 2026-05-31 11:24:24 +02:00
gullradriel 2bb3b415d1 update submodule (#3199) 2026-05-30 20:31:01 +02:00
Pezsma 6e0394fb6b Filename scroll (#3193) 2026-05-30 08:24:20 +02:00
Totoo da21ea7866 Fix flashing (#3188)
Fixed some bugs
2026-05-30 01:49:49 +02:00
未来方舟 2c434946cd Display if IQ inverted in DFU overlay menu while the algo decides to (#3196)
* init

* expose more info

* copilot suggest
2026-05-29 12:06:17 +02:00
gullradriel f3aee44e3b update submodule (#3187)
* update submodule
* fix iwyu CI errors
* fix sd over usb with hackrf submodule si5351c refactored functions
2026-05-21 17:48:53 +02:00
未来方舟 165c190d92 Use html comments to make som text only visible by PR creator (#3186)
* Use html comments to make som text only visible by PR creator

* reformat a bit
2026-05-21 15:51:56 +02:00
Totoo 8f9246c1ea Enhance transmitter model and UI for better handling of TX and RF ampstates (#3185) 2026-05-21 15:50:47 +02:00
Pezsma f1d52ae78f expanding channels (#3183) 2026-05-21 15:50:10 +02:00
qwer123 865bdee9b3 add external clock detect method (#3180) 2026-05-20 22:40:39 +02:00
Totoo 314e910cbc make battleship smaller (#3181) 2026-05-20 18:39:21 +02:00
Pezsma ec6c7eff86 Hard reset app (#3182) 2026-05-20 18:38:13 +02:00
Pezsma 95b2e9fbc3 Charge sleep bug (#3177) 2026-05-15 09:24:30 +00:00
未来方舟 52ff442fcc Tune pull_request_template a bit (#3176) 2026-05-14 00:48:27 +08:00
gullradriel 95d246a020 Update hackrf submodule (#3174)
* updated submodule

* usb_serial: populate usb_device before usb_run. Upstream now leaves the global usb_device zero-initialized and expects hackrf_usb.c to memcpy a per-board const into it. We don't link hackrf_usb.c, so do the memcpy here. Also flip control_in to enable_zlp=true to match the new endpoint API.

* baseband: derive IS_* board defines from BOARD. Upstream moved platform IS_* macros from platform_detect.h to cmake. Set them per BOARD here since we don't include platform-detect.cmake. Drop the duplicate clkin.c entry.

* sd_over_usb: drop duplicate driver objects, fix setup callback. Upstream now defines i2c0/i2c1, spi_bus_ssp0/1, sgpio_config and rf_path in the common driver files, so stop redefining them here and patch the runtime fields in pin_setup() instead. Update si5351c_reset_pll() call to the two-arg form. Replace the no-op transceiver_usb_setup_complete stub with one that delegates to usb_setup_complete.

* sd_over_usb: populate usb_device before usb_run. Same fix as the application side: memcpy a const template into usb_device before usb_device_init/usb_run, otherwise the descriptor pointer is NULL. Also pass enable_zlp=true on control_in to match the new endpoint API.

* fix unicode in comment

* remove lz4 bufs reservation in M0
2026-05-13 14:53:54 +02:00
Frederic BORRY c34a6940ca EPIRB RX app rework + EPIRB TX app fixes (#3172)
* First step to EPIRB rx app fix
* Added spike filtering on carrier detection for more robustness.
* Created dedicated packet builder
* First step to fixing UI.
* First step to epirb-rx ui rework.
* Made static methods static members
* Code optim
* Code cleanup + first step to new UI
* Fixed display
* Added BeaconList widget + BeaconDB
* Epirb TX app memory optim
* Fixed OOM when opening Map Display or Locator editor on EPIRB TX app
* First step to tabs + code cleanup to free some space...
* Added spectrum analyzer + fixed display
* Fixed display refresh issue after leaving spectrum.
* More code optim + first step to detail view.
* Fixed float coord display.
* Fixed protocol names
* Fixed location display
* Externalized country base to sd card + more code optim
* Fixed country display
* Fixed path
* Added protocol description and resource manager
* Added main/aux loc device
* Removed specan to save space
* First step to adding QR code
* Used a custom TextArea component instead of Console
* Fixed QR code display for map URL
* Added beacon selection management + fixes
* First step to map/data qr.
* QR tab : added data + detail/map toggle
* Removed sqhelch to save space
* Removed audio to save space
* Removed freq label to save space
* Removed packet timestamp to save space
* Moved frequency values to resource file
* Fixed detection parameters to suite real beacons (slower phase shift time).
* Fixed countdown field size
* Fixed frequency list.
* Fixed detail view on select.
* First step to fix settings.
* Fixed squelch for a 0-99 range.
* Added bch code correction (single bit error).
* More code optim, disable country cache, removed inline from location to reduce code size.
* Removed inline, code formatting.
* Added bch code correction display.
* Fixed focus bug, fixed packet size check. Added BCH1/BCH2 correction test cases in BEACONS.TXT file for EPIRB TX application.
* Fixed merge
* Added countdown setting.
* Added selection display to beacon list
* Restored squelch control. Added countdown reset.
* Code optim on formatSummary
* More code optim
* Made ResourceManager methods static
* Externalized beacon strings to res file
*Removed frame parameter from methods
* Added test beacons for emergency. Removed old res file.
* Fixed resource manager
* Fixed resources. Added emergency display.
* BCH + res fixes
* Fixed beacons
* Fixed app color. Fixed HexId and Serial calculation.
* Fixed frequency list. Comments.
* Code cleanup and comments
* Added slideshow mode to EPIRB TX application. Code comments / cleanup.
* Fixed frame end after wrong code cleanup
* More code optim and comments. Removed touch from beacon list.
* Added beacon selection with encoder on detail view.
* Added beacon paging in header. Fixed timeout display
* Fixed build for PRALINE
* Fixed formatting
* Potential fix for pull request finding
* stop on carriage return
* empty string if unknown
* zeroing data buffer before use
* fix typo
* add include
* fix case if pair == 0
* fix scpectrum_on to spectrum_on
* fix typos
* replaced 3 inlined offset blocks with apply_offset calls
* set_beacon consolidation
2026-05-12 14:00:47 +02:00
Synray 33330e4c34 Use gradient for level view, fix height scaling (#3173)
* Use gradient for level view, fix height scaling
* Add RSSI indicators
2026-05-12 12:48:17 +02:00
Pezsma 66f1835232 Wait tx complete (#3165)
* Implement radio transmission drain mechanism and update shared memory structure
* don't wait for timeout if baseband already in shut down state
2026-05-12 12:45:02 +02:00
Synray 4dfbf666d0 Add more waterfalls (#3170) 2026-05-10 09:37:08 +02:00
Synray 245a36459f UI and bug fixes (#3164)
* Fix BLE RX RSSI and Channel scale

* Properly reset gradient index 0

* Use move constructor in MenuView::add_item

* Avoid erasing during iteration

* Fix gradient rounding. Extend to end of table

* Add comment to add_item
2026-05-10 00:08:05 +02:00
janhaverkamp 590f19062b Add support for larger battery capacities (#3168) 2026-05-09 20:54:25 +02:00
Totoo f38ef4ebc8 Added CU8 playback support (#3166) 2026-05-09 12:23:43 +02:00
gullradriel 900d2f2482 fix missing header (#3167) 2026-05-09 12:23:10 +02:00
Totoo 5aff5ac277 Fix text color multiline handling (#3161) 2026-05-08 09:57:06 +02:00
Totoo cf4d245964 Fixes #3159 (#3160) 2026-05-07 23:54:30 +08:00
Pezsma c7f7173958 sleep warning fix (#3162) 2026-05-07 15:26:34 +02:00
Totoo 02c0f67a8d fix menu handling again (#3157) 2026-05-04 20:59:48 +02:00
Totoo 2bacbc80a8 Memory management improv. 2026-04-30 15:00:19 +02:00
Pezsma ab986a0378 Change charge display off to sleep (#3153) 2026-04-29 19:57:17 +02:00
Totoo c9f310f548 Add power off command to I2cDev_PPmod class (#3152) 2026-04-23 13:18:56 +02:00
Synray 1dafdd5a26 Increase text length to avoid cutoff (#3150) 2026-04-20 15:47:59 +08:00
VasylSamoilov fc5beb0c09 FLEX RX: fix long-address numeric decode, improve short message format (#3136)
- Fix long-address numeric message decoding: read body[0] from Vy (j+1)
  for multi-word messages, from w1 for single-word. Fix MF word count
  for long addresses (n_field words, not n_field+1).
- Fix long-address short message: read 5 additional BCD digits from Vy
  for 8-digit decode.
- Fix BCD table: index 10 is '.' (dot) not ' ' (space) in both RX and TX.
- Clean up address decode: identify address type by value range per
  Table 3.8.1-1. Remove is_group/is_temp_group from packet structs.
- Rename SMSG to SHORT. Format message payload directly in baseband:
  TONE, NUM abc, SRC N, SRC N N=M R=R, RESERVED hex.
- Add BIW1 heartbeat packet (biw_field=0xFF) on every decoded frame.
- Move timezone table to file scope, simplify console log_message,
  add No signal initial status, simplify serial BIW output format.
2026-04-18 21:15:05 +02:00
gullradriel 3f10663bc9 Pull request guidelines (#3149)
* update PR template
* wiki mention
* added trusted contributors guidelines, compile proof simplification
2026-04-18 21:13:30 +02:00
VasylSamoilov 396f8aef41 POCSAG: show '?' for characters from uncorrectable codewords (#3138)
Use per-character error tracking to substitute '?' when a
character's bits came from an uncorrectable codeword (BCH error_count >= 3). 
Previously these displayed as garbage.
2026-04-18 18:56:33 +02:00
VasylSamoilov c095a88cb3 FLEX TX: fix stop button not stopping transmission (fixes #3135) (#3137)
Reset tx_phase_ to 0 on stop so on_tx_progress does not restart
the next phase. Reset progress bar and repaint capcode info.
2026-04-13 15:57:31 +08:00
Totoo 900c80549f externalize basebands, not needed, and remove too high level function (#3134)
* externalize basebands, not needed, and remove too high level function

* fix ert
2026-04-10 21:08:46 +02:00
VasylSamoilov 4dce613172 FLEX TX: pager transmitter with long address support (1600/2FSK) (#3132)
External app for encoding and transmitting FLEX pager frames at
1600bps/2FSK. Supports short (1-1,933,312) and long (2,101,249-
4,297,068,542) addresses across all encoding sets (1-2, 1-3/1-4, 2-3).

Message types:
- Alphanumeric (vector type 5): 7-bit chars, signature, K checksum
- Standard Numeric (vector type 3): 4-bit BCD, K checksum
- Short/tone-only (vector type 2): space-filled BCD in vector word
- Short numeric (vector type 2): BCD digits in vector word (3/8 digits)

Frame structure:
- FIW (Frame Information Word): cycle, frame, roaming flag
- BIW (Block Information Word) support with up to 3 extra words:
  Date, Time, SysInfo/timezone+DST, SSID1 (Simulcast System ID:
  Local ID + Coverage Zone), SSID2 (Country Code)
- BCH(31,21) encoding with bit reversal, block interleaving
- Two frames per TX: message R=1 (new) then R=0 (duplicate)
- Message number N (0-63): random seed from RTC, auto-increments
- ERS (Emergency Re-Sync) burst: configurable N x 42-cycle bursts
  with 8-cycle preamble per frame

UI:
- Capcode info line: address type, computed frame/phase (reference)
- TX status: step counter with ERS/frame progress
- Set params view: Date/Time/TZ/DST, LocID/CovZone, CountryCode,
  Roaming, message number, ERS count
- Date/time auto-populated from RTC with calendar-equivalent year
  mapping for years beyond 2025 (per ARIB STD-43A)
- Year validation with equiv-year popup for out-of-range values
- Capcode and BIW flags persisted via SettingsManager

Serial interface:
- sendflex <capcode> <type> <msglen> + payload
- Types: 0=alpha, 1=numeric, 2=short/tone, 3=short numeric
- FlexTosendMessage (ID 103) for serial-to-app communication
- App must be open to receive serial commands
2026-04-10 11:17:52 +02:00
VasylSamoilov cf44076a34 FLEX RX: comprehensive decoder rewrite (#3131)
Baseband (proc_flex.cpp):
- Fix numeric vector field width (3-bit n+1 for types 3,4,7)
- Fix capcode range classification for long address components
- Add long address decode (Set 1-2, 1-3/1-4, 2-3)
- Add vector checksum validation (4-bit nibble sum)
- Add tone-only boundary detection via vector pre-scan
- Add idle phase detection (skip all-zeros/all-ones before BCH)
- Skip spurious TON for capcode 1 (BCH-corrected idle artifact)
- Add BIW parsing: SSID1/SSID2, DATE, TIME, TZ, SYSMSG, CHAN
- Add alpha fragment flags: frag, more_frag, seq, new, maildrop, sig
- Add secure message enc= field (alpha/binary/separate)
- Add numbered numeric flags: seq, new, fmt
- Add HEX/Binary header decode: block_bits, new, maildrop, rtl
- Add short instruction decode: temp group slot/target, sys events
- Add short message (SMSG) decode: tone/numeric/source/numbered
- Add group/temp-group/priority flags per address
- Add S2 C-pattern detection with ±1 symbol boundary correction
- Extract FIW roaming, repeat, traffic fields
- Replace snprintf with lightweight str_* helpers (no heap/_sbrk)
- Mark uncorrectable BCH words as ? instead of aborting phase
- ETX/NUL handling: trim trailing padding, show mid-message as ?

App (ui_flex_rx.cpp):
- Add status bar: cycle/frame, bitrate, polarity, time, timezone
- Add network info bar: LID, CZ, CC, roaming flag
- Pipe-delimited serial output for all message types
- BIW events to serial and status bar (not console)
- Human-readable console: +GRP for temp group, G/TG/P flags
- USB serial via UsbSerialAsyncmsg

Packet (flex_defs.hpp):
- uint64_t capcode (long address support)
- 256-byte message buffer
- Fragment flags, BIW raw values, address type, group/priority

Known limitation: phase label unreliable on multi-phase modes
(3200/2FSK, 6400/4FSK). Data always decodes correctly.
2026-04-10 08:37:38 +02:00
VasylSamoilov dd006b4830 POCSAG: decoder fixes, serial output, TX polarity cleanup (#3130)
RX: fix address/batch-boundary handling, add numeric continuation,
add heuristic type detection toggle, pass inverted polarity flag,
stream decoded messages over USB serial.

TX: rename phase to polarity (Standard/Inverted), fix function
validation (&&→||), fix serial shell payload read (offset tracking,
31-byte cap, preserve msglen).

Decoder: remove color escapes from output (plain text for all
consumers), guard count_alpha_fill against empty string, keep
numeric_len across continuation batches.

Serial format: POCSAG <baud> <addr> <func> <pol> <type> "<msg>" hex:<hex>
Quote escaping for embedded " and \\ in alpha messages.
2026-04-10 08:35:43 +02:00
qwer123 fc18992442 Add retry mechanism for volume write (#3121) 2026-04-10 08:33:48 +02:00
gullradriel ef2ae9874b fix build (#3129)
Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-04-08 08:28:44 +02:00
Pezsma 5deeaed5f0 Praline rssi (#3127) 2026-04-07 19:42:21 +02:00
Ryan Harden d5f94398ce Add external 2-Tone TX/RX Apps (#3128) 2026-04-07 19:32:41 +02:00
Totoo fba15c48e5 Battery capacity options added (#3125) 2026-04-04 09:19:17 +13:00
Totoo 888634ce35 praline without the much fixed ram (#3123) 2026-04-04 09:17:21 +13:00
Frederic BORRY 03131c8a0c EPIRB TX application update (#3120) 2026-04-03 11:35:46 +02:00
gullradriel 605c9efa0c Make detector use freqman (#3119)
* make detector list a freqman file
* add file loading ability, autoscan/pause, step in on pause on range, frequency value display
* Update firmware/application/external/detector_rx/ui_detector_rx.hpp
* Update firmware/application/external/detector_rx/ui_detector_rx.cpp
* add 750 kHz step
* revamp UI and add auto advance, auto scan, and only display 'RANGE SCAN...' when auto scan is on on ranges to avoid flicker of the hell.
* adjust as I can the ranges as they where before
* Update sdcard/FREQMAN/DETECTOR.TXT
* fix instance related copilot remark
2026-04-02 08:26:45 +02:00
gullradriel aee54e5b2d Merge updated submodule (#3116)
* update hackrf submodule
* add platform_gpio.c and platform_scu.c to baseband build
    -New source files required by the updated hackrf submodule.
* adapt scsi.c to updated hackrf API
    -Rename struct gpio_t to struct gpio and add gpio_lpc.h include.
* adapt hackrf_core.c to updated hackrf API
    - Rename struct gpio_t to struct gpio throughout
    - Add platform_gpio.h, platform_scu.h, and fixed_point.h includes
    - Update pin_setup() to use platform_scu() accessor instead of SCU_ macros
    - Rewrite sample_rate_set() to use new fixed-point frequency API
    - Update ssp_config_w25q80bv for changed struct layout
    - Update si5351c and max283x function call signatures
2026-03-30 23:47:39 +08:00
zxkmm e33e697cca remove some bluffing text from fpv detect app (#3114) 2026-03-29 17:24:29 +08:00
zxkmm d7d27d99e7 edit security policy (#3113) 2026-03-28 18:14:22 +01:00
Pezsma 78d3480b68 Morse tx update (#3112)
* loop message, progress bar
2026-03-27 18:29:20 +01:00
Totoo 70b42f4a56 fix audio mode change crash (#3111) 2026-03-26 22:40:27 +01:00
jLynx 5c719645d6 Update version.txt 2026-03-24 22:31:30 +13:00
jLynx b0eb776bdd Update past_version.txt 2026-03-24 22:31:18 +13:00
jLynx ce87161f9b Revert "Switched build to use ninja by default (#3072)" (#3102)
This reverts commit 22932e636d.
2026-03-20 12:16:14 +13:00
Totoo 8883f2decc fix sd test app mem usage (#3101) 2026-03-17 15:42:10 +01:00
Totoo e06d23ea3e app color 2026-03-17 15:41:31 +01:00
Totoo 0da90c7c5e modified app colors based on wiki (#3099) 2026-03-17 15:00:25 +01:00
Totoo e7572e5d41 transparent bg on map marker text (#3098) 2026-03-17 21:46:05 +08:00
Totoo 50f1e51d7e pocsag fix (#3097) 2026-03-17 21:35:37 +08:00
Sarah Rose 55a15958ec Add P25 Phase 1 TSCC TX external app (p25_tx) (#3090)
* Add P25 TX external app (UI shell)

- Register p25_tx in external.cmake and external.ld at 0xAE010000
- UI with NAC, SYSID, WACN, RFSSID, SITEID, TG, VCH fields
- 2.4 Msps TX, 155 MHz default, no baseband (m4_app_tag={0,0,0,0})

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

* Add P25 TX baseband processor (M4)

- P25TxConfigure = 102 in message.hpp + P25TxConfigureMessage
- image_tag_p25_tx in spi_image.hpp
- set_p25tx_data() in baseband_api (copies dibits to bb_data, sends msg)
- proc_p25_tx registered in baseband CMakeLists (tag P25T)
- m4_app_tag updated to {'P','2','5','T'}
- TX wired: transmits P25 FSW test pattern (512 dibits) via M4 C4FM proc
- on_tx_progress handler stops TX when frame complete

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

* p25_tx: implement correct P25 TSBK encoding from ccemu

- BCH(63,16) via generator matrix (not polynomial division)
- CRC-CCITT bit-by-bit init=0 over 80 bits
- 1/2-rate trellis encoder (4 states)
- DataInterleave per TIA-102
- Status symbol insertion (SS=0x02 every 35 dibits)
- TSBK rotation: IDEN_UP -> NET_STS -> RFSS_STS -> NET_STS
- tsbk_idx_ persists across buffer refills, resets on stop_tx

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

* p25_tx: apply clang-format

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

* p25_tx: fix HackRF lockup - zero output buffer when idle

When configured=false, execute() returned without writing to the
output buffer. The DMA would re-use stale IQ samples from the
previous cycle, sending garbage to the HackRF and causing it to
lock up mid-transmission.

Fix: explicitly zero the output buffer when the proc is idle.

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

* p25_tx: address PR review feedback and fix TX restart lockup

- Replace NumberField with SymField::Type::Hex for NAC, SYSID, WACN,
  RFSSID, SITEID, TG, VCH (hex values should display as hex)
- Add voice grant TSBK (OpcodeGrpVChGrant 0x00) to rotation cycle;
  rotation is now 5-step: IDEN_UP->NET_STS->RFSS_STS->GRP_V_GRANT->NET_STS
- Wire TG and VCH fields into fill_tx_buffer for voice grant encoding
- Reset txprogress_message.done=false on new P25TxConfigure in baseband
- Add #include <cstring> to baseband_api.cpp (needed for memcpy)
- Fix TX restart lockup: remove baseband::shutdown() from stop_tx() so
  the M4 stays loaded between sessions; only shut down in destructor.
  Previously, the stale ShutdownMessage in the queue would be picked up
  by the restarted M4 on second TX, causing it to exit immediately and
  leaving the HackRF stuck in TX mode.

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

* ifndef guard instead of pragma
2026-03-17 08:41:01 +01:00
stafur 395e20d17b Next praline clean up (add SW: RSSI for hackrf pro praline) (#3091)
* Cleaned up #ifndef PRALINE and updated logic to being with #ifdef PRALINE entries where possible to make logic flow for PRALINE code execution pipeline clearer. Cleaned up compiletime warnings for PRALINE related codebase updates.

* Addressed comments provided by copilot during PR review. Combed through frequency definitions for consistency between PLL A and PLL B register definitions for CLKs 0-7. Ensured CLK3/LK6 <- SMA PORTs and CLK7 <- not utiliized are disabled during core development phase to support root cause analysis of any spectral artifacts. Updated MCU frequency to 40MHz to ensure audio harmonics are outside FM radio band range (< 80 MHz, >120MHz) and added comments clarifying choice of 40 over 10 MHz for potential future root cause analysis in other bands where audio may be expected as needed. Added CLK6 and CLK7 to Clocks Status View Debug display. Moved CLK defintions and PLL instantiations for components that are most RF sensitive to PLL A. Left others in PLL B. That is move FPGA CLK1 to PLL B, while moving CLK2, CLK4, and CLK5 to PLL A.

* Cleaned up PLL A and B XTAL reference checks relative to 800 MHz.

* Encapsulated HackRF Pro Praline debug and status vies into a single Pro Debug submenu as part of clean up.

* Fixed BLE RX Out of Memory error. Updated LPC43xx ld scripts to accouint for additional HackRF Pro praline memory.

* Addressed copilot comments for ble_rx_app by adding recent_entries_view.set_dirty. Updated ble_rx_app for easier use with heap limit set to one less than recent entries max limit.

* Addressed copilot comments by updating comment clarity in source files. Updated ui_debug to allow for return reference if set for PRO debug menu item.

* Improved readability of intialization parameters for the FPGA registers, and addressed 20Mhz nulls by initializing DC Notch width with standard setting, and DC Adaptiation rate with a balanced setting.

* Ran format-code.sh

* Added option to allow for user to set number if entries in recent list. Default is set to a relatively stable 32.

* Removed #ifdef PRALINE pragmas from ble_rx_app such that HackRF One can also use the updated UI widget to allow for user to set number of entries in recent list.

* Ran format-code.sh

* Improved RSSI for praline. Values are now being counted correctly by statistics methods after selecting ADC = 0 0 for RSSI. This was different than for the hackrf one which uses ADC = 1.

* Implemented SW RSSI calculation since HW RSSI not currently available in hackrf pro (praline) via fpga pass through. Calibrated RSSI power sensistivity to mid level signal intensitities.

* In preparation for WIP allocated full 128k to M4 limiting heap for M0 to 0k, and leabing it only with 64k from bank 0. That is, bank1 and bank2 are fully allocated to M4. Cleaned up #ifdef PRALINE conditionals such taht max2837/39 are in #else conditionals, as well as continuing to clean up hackrf_r9 booleans by placing them within #else conditionals.

* fix typo

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

* Addressed co-pilot comments. Clarified use of 25 recent samples in ble app for stability, udpated comments to reflect use of avg power over pwak pwer detection, improved safety of sample packing while relying on intrinstics for execute the implementation in a single machine instruction (PKHBT) for computational and memory efficiency.
2026-03-17 08:13:09 +01:00
zxkmm 9bf1f2175e fix bad color (#3096) 2026-03-17 08:10:07 +01:00
zxkmm ec57f99a2c Contribution template (#3088) 2026-03-16 19:41:43 +01:00
Sarah Rose 5c2d1d3dfb Add MDC-1200 TX external app (#3083)
Implements MDC-1200 single and double-packet TX as a PPMA external app.

Features:
- Supports PTT ID, Emergency, Radio Check, Stun/Unstun, Call Alert,
  Selective Call (1 & 2), Status Request/Response, Remote Monitor, Message
- Auto-fills Arg byte on Op selection
- AFSK FSK modulation: 1800 Hz mark / 1200 Hz space, 1200 baud
- NRZI encoding with Golay FEC per MDC-1200 spec
- Tap-to-edit frequency via keypad
- Default TX gain of 30 dB
- Uses AFSK baseband (same as POCSAG/SAME TX)
- All buffers kept off the stack to avoid hard faults in ext app environment

* mdc_tx: address PR review feedback

- Move MDC helpers into ui::external_app::mdc_tx namespace (drop anonymous namespace)
- Use AFSK_TX_SAMPLERATE macro instead of hardcoded 1536000
- Add #include "modems.hpp" explicitly for AFSK_TX_SAMPLERATE
- Replace n*8/n*16 pixel positions with UI_POS_X/UI_POS_Y macros
- Fix field comments: 'hex bytes' -> 'decimal bytes (0-255)'
- Fix TransmitterView frequency_step: 1750000 -> 12500 (12.5kHz NFM step)
- Fix MDC preamble: replace leading 0x00 bytes with 0xAA
  after NRZI encoding stayed 0x00, terminating TX on the first word.
  0xAA bytes NRZI-encode to 0xFF (continuous 1800Hz mark tone), which
  is the standard MDC-1200 preamble and does not trigger the sentinel.
2026-03-16 19:28:07 +01:00
berkeozkir 52dd2ea2bb Add FPV DETECT external app with Auto-scan FPV Presence Receiver (#3084)
* fpv_rx external app implemented

* FPV DETECT: PR feedback and Copilot suggestions

- Rename app from 'FPV RX' to 'FPV DETECT' (menu and title)
- Add fpv_rx to EXTAPPLIST in external.cmake so app is included in build
- Set external_apps_address_end = 0xAE010000 in external_app_info.py
- Fix clang-format in ui_fpv_rx.cpp (update_visual_state block)
- Re-sample adjacent channels during candidate verification so
  neighbor_margin lock decision uses current measurements instead of
  stale channel_memory_ from scan (reduces false/missed locks)

Made-with: Cursor

* Rename fpv_rx to fpv_detect (files, dir, app id)

- Rename directory external/fpv_rx -> external/fpv_detect
- Rename ui_fpv_rx.{cpp,hpp} -> ui_fpv_detect.{cpp,hpp}
- Update external.cmake and external.ld for fpv_detect
- Namespace fpv_rx -> fpv_detect, class FpvRxView -> FpvDetectView
- Section/symbol .app_fpv_rx -> .app_fpv_detect

Made-with: Cursor
2026-03-16 10:28:07 +08:00
zxkmm b273165a0c fix warning and ub (#3089) 2026-03-14 00:51:15 +08:00
Frederic BORRY 5aecd4097e EPIRB TX Application (#3081)
* First step to epirb-tx app

* Next step to epirb_tx app

* Fixed merge

* EPIRB TX app progress

- Fixed UI
- Added automatic frame resend after delay
- Added load of beacons from EPIRB/BEACONS.TXT file

* First step to adding 121.5 MHz signal

* Refactored 121.5 signal generation code

* Added frequency change when switching from BPSK to AM

* Added checkbox for AM signal.

Added START/STOP button
Added AM frequency
First step to editor mode.

* Next step to frame editor

* Fixed location bits.

* Refactored code + fixed location

* Added location fields

* UI fixes + settings backup

Fixed lat/long display
Added send on change checkbox
Added settings backup
Fixed locator edition
Fixed BEACONS.TXT to move to Self test beacons.

* Update ui_epirb_tx.cpp

Fixed timeout reset

* Added national location protocol.

Added national location protocol.
Fixed send on change.

* UI fixes

Added country selection
Separated beacon protocol and beacon type selectors

* Standard protocol

Added standard protocol
Added internal checkbox

* Fixed fomratting

* Code cleanup

* Comments / doc

* More comments / doc

* More comments / doc

Fixed pre count duration.
Added frequency restore after leaving

* Prepare merge

* Prepare merge

* Finished merge

* More comments and doc

* Update ui_epirb_tx.cpp

Fixed out of memory error when using AlphanumView

* Update proc_epirb_tx.hpp

Fixed comment

* Fixed copilot PR comments.

* Update proc_epirb_tx.hpp

Fixed formatting

* Update external.ld

Merge with kiss_tnc app
2026-03-12 22:05:42 +13:00
qwer123 2c2959df30 change pin mux (#3085)
Co-authored-by: fuwenbo <16166717+fuwenbo95@user.noreply.gitee.com>
2026-03-12 20:24:42 +13:00
stafur 9a3c2cc6ac Next praline clean up (fixed BLE RX crashes) (#3080)
* Cleaned up #ifndef PRALINE and updated logic to being with #ifdef PRALINE entries where possible to make logic flow for PRALINE code execution pipeline clearer. Cleaned up compiletime warnings for PRALINE related codebase updates.

* Addressed comments provided by copilot during PR review. Combed through frequency definitions for consistency between PLL A and PLL B register definitions for CLKs 0-7. Ensured CLK3/LK6 <- SMA PORTs and CLK7 <- not utiliized are disabled during core development phase to support root cause analysis of any spectral artifacts. Updated MCU frequency to 40MHz to ensure audio harmonics are outside FM radio band range (< 80 MHz, >120MHz) and added comments clarifying choice of 40 over 10 MHz for potential future root cause analysis in other bands where audio may be expected as needed. Added CLK6 and CLK7 to Clocks Status View Debug display. Moved CLK defintions and PLL instantiations for components that are most RF sensitive to PLL A. Left others in PLL B. That is move FPGA CLK1 to PLL B, while moving CLK2, CLK4, and CLK5 to PLL A.

* Cleaned up PLL A and B XTAL reference checks relative to 800 MHz.

* Encapsulated HackRF Pro Praline debug and status vies into a single Pro Debug submenu as part of clean up.

* Fixed BLE RX Out of Memory error. Updated LPC43xx ld scripts to accouint for additional HackRF Pro praline memory.

* Addressed copilot comments for ble_rx_app by adding recent_entries_view.set_dirty. Updated ble_rx_app for easier use with heap limit set to one less than recent entries max limit.

* Addressed copilot comments by updating comment clarity in source files. Updated ui_debug to allow for return reference if set for PRO debug menu item.

* Improved readability of intialization parameters for the FPGA registers, and addressed 20Mhz nulls by initializing DC Notch width with standard setting, and DC Adaptiation rate with a balanced setting.

* Ran format-code.sh

* Added option to allow for user to set number if entries in recent list. Default is set to a relatively stable 32.

* Removed #ifdef PRALINE pragmas from ble_rx_app such that HackRF One can also use the updated UI widget to allow for user to set number of entries in recent list.

* Ran format-code.sh
2026-03-10 21:40:43 -04:00
zxkmm c91a6db165 remove redundant code from #3078 (#3082) 2026-03-10 16:13:10 +01:00
Sarah Rose bf5ea0a004 Add KISS TNC external app for APRS RX/TX over USB serial (#3078)
* Add KISS TNC external app for APRS RX/TX over USB serial

Adds a KISS TNC (Terminal Node Controller) app that bridges APRS
packets between the HackRF radio and a connected PC over USB CDC
serial using the KISS protocol.

Features:
- APRS receive at 144.390 MHz (configurable), decoding packets via
  the existing APRS RX baseband (PAPR.bin)
- Received AX.25 frames forwarded to host as KISS-framed data over
  USB serial
- KISS frames received from host transmitted as AFSK audio using the
  existing AFSK TX baseband (PAFT.bin)
- Clean RX->TX->RX baseband switching with proper timing delays
- USB connection status indicator with periodic refresh
- Compatible with standard KISS TNC clients (Xastir, APRSISCE/32,
  Dire Wolf, etc.)

Firmware changes required to support the external app:
- baseband_api.cpp: mark set_aprs() __attribute__((used)) so it is
  retained by the linker for external app use
- protocols/ax25: add make_frame_from_raw() to build NRZI bitstream
  from raw AX.25 bytes, marked __attribute__((used))
- usb_serial_host_to_device: add kiss_raw_handler hook to route
  incoming USB bytes directly to a registered callback, bypassing
  the shell; marked __attribute__((used))
- baseband/proc_aprsrx: add constructor that auto-configures at
  1200 baud so external apps do not need to call set_aprs() at init

* Address PR review: generic RAII USB handler, fix TX cutoff and buffer overflow

- Replace KISS-specific set_kiss_raw_handler() in shared USB code with a
  generic UsbSerialInputHandler RAII class; any app can now register a
  handler via the ctor and it auto-clears in the dtor — no app-specific
  'if' checks in shared code, no __attribute__((used)) needed
- Remove __attribute__((used)) from set_aprs() — it is referenced directly
  by ui_aprs_rx.cpp so it will never be dead-stripped
- Fix send_kiss_frame() buffer overflow: per-byte bounds check prevents
  2-byte escape sequences from writing past the end of the output buffer
- Replace fillOBuffer(TIME_INFINITE) with chOQWriteTimeout(TIME_IMMEDIATE)
  to drop bytes instead of blocking the event loop
- Remove chThdSleepMilliseconds() calls from start_tx() and finish_tx()
- Fix TX cutoff bug: call start_tx() directly from process_kiss_frame()
  instead of deferring via tx_pending_ flag (deferral caused kiss_idx_
  to be 0 by the time start_tx() fired, producing an empty frame)

* Move USB write into UsbSerialInputHandler class

Add a write() method to UsbSerialInputHandler so all USB I/O
(both input callback and output) is encapsulated in the class.
KISS TNC now uses usb_input_handler_->write() instead of calling
chOQWriteTimeout(&SUSBD1.oqueue, ...) directly, keeping raw USB
details out of app code.

* Address PR review: remove auto-configure from APRSRxProcessor, add timeout param to write()

- Remove default_config from APRSRxProcessor() constructor; baseband should
  start unconfigured and wait for APRSRxConfigureMessage from the app side
  (already sent via baseband::set_aprs() in KISS TNC and APRS RX app)
- Remove stray #include "stdio.h" from proc_aprsrx.cpp
- Add systime_t timeout parameter with default TIME_IMMEDIATE to
  UsbSerialInputHandler::write() so callers can choose blocking behaviour
2026-03-10 10:40:27 +01:00
Erwin Ried bf166bb2fc Update main video 2026-03-08 21:32:39 +01:00
stafur 52239d320a Next praline clean up (#3077)
* Cleaned up #ifndef PRALINE and updated logic to being with #ifdef PRALINE entries where possible to make logic flow for PRALINE code execution pipeline clearer. Cleaned up compiletime warnings for PRALINE related codebase updates.

* Addressed comments provided by copilot during PR review. Combed through frequency definitions for consistency between PLL A and PLL B register definitions for CLKs 0-7. Ensured CLK3/LK6 <- SMA PORTs and CLK7 <- not utiliized are disabled during core development phase to support root cause analysis of any spectral artifacts. Updated MCU frequency to 40MHz to ensure audio harmonics are outside FM radio band range (< 80 MHz, >120MHz) and added comments clarifying choice of 40 over 10 MHz for potential future root cause analysis in other bands where audio may be expected as needed. Added CLK6 and CLK7 to Clocks Status View Debug display. Moved CLK defintions and PLL instantiations for components that are most RF sensitive to PLL A. Left others in PLL B. That is move FPGA CLK1 to PLL B, while moving CLK2, CLK4, and CLK5 to PLL A.

* Cleaned up PLL A and B XTAL reference checks relative to 800 MHz.
2026-03-08 13:39:53 +13:00
E.T. 561143cfb0 Improve SD Card Status Display and Error Handling (#3079)
Summary:
Fixed SD card mount error detection by enabling immediate mounting, made the status icon clickable to access settings, and added real-time status display (including filesystem type) in the SD card settings page.

Changes:
- Changed f_mount() to immediate mounting for error detection
- Made SD card status icon clickable, linking to an enhanced sd card settings page that now includes live display of card status and filesystem type
- Removed "SDCard Error" menu item
2026-03-06 20:05:44 +01:00
jLynx 678ee2963e PortaRF & Windows tool update (#3075)
* Remove outdated DLL files and update HackRF submodule to latest commit

* Remove outdated msvcp120.dll and msvcr120.dll files
2026-03-06 09:41:56 +13:00
Sarah Rose bae3854d27 Add SAME TX external app (#3069)
* Add SAME TX external app

Adds a Specific Area Message Encoding (SAME/EAS) transmitter as an
external app (.ppma) for HackRF PortaPack.

SAME is the digital protocol used by NOAA Weather Radio and the
Emergency Alert System in the US and Canada for broadcasting
emergency alerts (tornado warnings, flash floods, EAN, etc.)
on 162.400-162.550 MHz and AM/FM broadcast stations.

Features:
- Originator code selector (WXR, EAS, CIV, PEP)
- Event code selector: 26 standard SAME event codes
  (RWT, TOR, SVR, FFW, EAN, etc.) with live name display
- FIPS location code entry (state 00-99 + county 000-999)
- Purge time entry (hours + minutes)
- Live message preview showing the encoded header
- AFSK encoding: mark=2083Hz, space=1563Hz, 520.833 baud
- FM deviation: 5 kHz, repeated 3x per SAME spec
- Default frequency: 162.550 MHz (WX channel 7)

Protocol details:
- 16-byte 0xAB preamble (bit-reversed for LSB-first AFSK)
- Header: ZCZC-ORG-EVT-SSPCCC+HHMM-JJJHHMM-STATION-
- Uses existing PAFT (proc_afsk) baseband image

* Apply clang-format via format-code.sh

* Add GPL license headers, guard re-entry, call set_transmitting(true)

- Add standard PortaPack GPL-2.0 license header to ui_same_tx.cpp and .hpp
- Guard start_tx() with tx_active_ to prevent re-entrant calls
- Call tx_view.set_transmitting(true) after starting TX so the
  TransmitterView button switches to stop mode
2026-03-05 19:49:17 +13:00
jLynx 0645f1897a Flashing: overhaul mayhem_flasher.bat with serial fallback and improved UX (#3074)
* Refactor mayhem_flasher.bat to streamline firmware flashing process and add serial fallback for device connection

* Improve COM port selection logic in mayhem_flasher.bat for better user input handling

* Enhance error handling in firmware flashing process for improved user feedback
2026-03-05 16:20:41 +13:00
jLynx da0a5c3e73 Fix on-device flash utility hanging on HackRF Pro (Praline) (#3073)
* Add support for W25Q32JV device ID in wait_for_device function

* Enhance mayhem_flasher.bat: Add DFU unbrick option and update firmware links
2026-03-05 14:49:32 +13:00
jLynx 22932e636d Switched build to use ninja by default (#3072)
* Initial plan

* Enable Ninja parallel builds for nightly and stable release pipelines

Co-authored-by: jLynx <4393979+jLynx@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-03-05 13:25:39 +13:00
E.T. 2b7302e659 rename Widget::visible to Widget::drawn (#3066)
* rename Widget::visible to Widget::drawn

* Update firmware/standalone/common/ui/ui_widget.cpp

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

* Update firmware/standalone/common/ui/ui_widget.hpp

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

* fix fm_radio usage of drawn()

---------

Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 21:30:43 +01:00
jLynx 60ce89021a Combine per-device firmware ZIPs into a single unified flasher (#3070)
* Add combined firmware ZIP creation and update flashing script for device selection

* Refactor firmware flashing scripts and update README for improved device selection and actions

* Remove outdated flash script for PortaPack Mayhem

* Add write permissions to release jobs in nightly and stable release workflows

* Rename HackRF firmware files for consistency in nightly and stable release workflows

* Refactor release workflows to streamline asset preparation and upload process

* Fix file paths for HackRF firmware in nightly and stable release workflows

* Updated labels

* Refactor mayhem_flasher.bat for improved clarity

* Added reset after flash

Updated the flashing script to include error handling and improved user prompts.

* Remove --reset from DFU as it is known to cause issues
2026-03-05 08:40:39 +13:00
jLynx 25556428a7 Update HackRF flashing utilities for Windows (#3067)
* Update flasher for windows

* Updated scripts

* Fix echo statement formatting in flash script

* Fix echo statement formatting in flash script
2026-03-04 07:22:19 +00:00
stafur 584587d814 Resolve "crackling" sounds in analog_audio_app (#3068)
* Updated PRALINE audio processing methods to account for potential slip in 25MHz reference clock. Moved all clocks to PLLB except for CLK0 to isolate the ADC from potential EMI and clock spills from the other clocks to CLK0. Added PLL reference to Clock ui_debug display and added debug view for PLLA and PLLB. Updated PRO menu in analog_audio_app to allow for 100Hz resolution in sample rate for finer control of samplerate debugging of crackling noise artifacts. Updated ppb method and set it for account for a potential 49 Hz shift in the PRALINE crystal. Started instatiating methods for synchronized DMA transfers for the spectrogram since the FFT calculations and the LCD data updates appear to be related to the crackling noise.

* Added firmware/flash.h to .gitignore.

* Changed reference clock for start_audio_pll to 40MHz instead of ~12MHz xtal. Moved clocks around to reflect best audio quality in analog_audio_app at 3.072 MHz sample frequency.

* Ran format-code.sh.

* Removed ppb in set_sampling_frequency since it created aliasing in analog_audio_app.

* Expanded baseband_bandwidth back to 1750000 after testing since audio quality was retained.

* Updated comments to address copilot recommendations where acceptable.

* Removed firmware/flashsize.h from .gitignore as coordinated with @gullradriel.
2026-03-04 02:11:19 -05:00
Erwin Ried a0179117b1 Update README
Broken photo and links updated
2026-03-04 00:48:17 +01:00
gullradriel ad577a1c66 Delete firmware/flashsize.h (#3063)
Remove uneeded file
2026-03-02 23:43:23 +01:00
E.T. b9958a689b Btngrid better paging (#3061)
* improved paging for ui_btngrid

* move pgup and down to their own funcs in btngrid

* one line minus (format code)

* visible check before focus
2026-03-02 23:33:09 +01:00
E.T. 3d8bc308ee add checks to prevent crashes when showing an empty menu (#3060)
* add checks to prevent crashes when showing an empty menu

* Update firmware/application/ui/ui_btngrid.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-02 21:04:35 +01:00
zxkmm 9ca36394d0 add oscilloscope (time sink) app (#3059)
* POC

* submodule

* seperate bb

* seperate bb

* seperate bb

* seperate bb

* seperate bb

* sadly have to reduce history

* icon

* bring it back

* bring it back

* bb

* format and move bb outside
2026-03-02 19:17:02 +01:00
E.T. f489d43259 Auto update ext apps (#3046)
* first naive impl

* Unregister sdcard notification

* btngrid reload_items fixed

* format-code

* Avoid some unnecessary updates

* clear empty space after removing items

* Fix displaying sdcard error after inserting card while running

* format code

* Eliminate multiple update_items() calls from on_populate()
And call it only once after on_populate()

* update_items() only once when showing btngrid

* normalize offset when menu item count decreases
2026-03-02 12:35:45 +01:00
stafur 6b663894b0 Addresses comments from PR #3057 (#3058)
* Addressed clock inversion and fractiona/interger states. Created ui_debug display that checks expected clock frequencies for expected 3.072 MHz sampling frequency commonly utilizaed in portapack apps.

* Ran format-code.sh

* Addressed comments from PR. Ran format-code.sh. Updated definition of a, b, and c in comments and introduced x, y, and z for debug display. Changed default color for P2/P3 conditional debub display for MS_DIV. Removed implied recalibration since this did not address crackling sound.
2026-03-01 21:51:28 +01:00
stafur b8ca356ce3 Addressed clock inversion and fractional/interger states for 3.072 MHz sampling rate (#3057)
* Addressed clock inversion and fractiona/interger states. Created ui_debug display that checks expected clock frequencies for expected 3.072 MHz sampling frequency commonly utilizaed in portapack apps.

* Ran format-code.sh
2026-03-01 21:28:36 +01:00
stafur 3a54b112ec Imrpoved sound quality in analog_audio_app, and added Nyquist protection option to BBW filter for praline. (#3054) 2026-02-28 23:36:01 +01:00
stafur b013e68b17 Sampling frequency and Receiver Model tweaks. (#3053) 2026-02-28 10:11:04 +01:00
stafur b2bf0f2459 Legacy Initialization, Analog Audio App, and Aliasing fixes. (#3052)
* Added PRO button to analog_audio.cpp for debugging metallic ringing sounds in Audio app. Ensured consitency to 4MHz in analog_audio.cpp

* Restored blutooth after loss.

* Added several sampling rates to analog_audio for testing.

* Ran format-code.sh

* Fixed 0x03 mode for DC/Q-INV/Q-SHFT being set at every tuning and sample rate change. This was the root cause for loss of bluetooth before. Sample rate, and frequency can now be changed without need for manually resetting DC/Q-INV/Q-SHIFT settings. Updated method for setting frequncies in praline so that we have more testing options.

* Ran format-code.sh and cleaned up stale comments.

* Addressed comments, and removed commented line, opting for higher register values, 5E and 5D. Added WFM Debug View to support testing demodulation ringing.

* Addressed comments in PR conversation to clean comments and ensure consistency at initializtion accross updated methods and displays.

* Ran format-code.sh

* Set initial legacy state. Improved readability of clocking initialization settings. Updated set_sampling_frequency, and udpate_bandwidth to set decimation values in the fpga_registers to avoid aliasing in low band frequencies.

* Updated clock_manager to use correct clock and clock parameters for audio pll. Ran format-code.sh.
2026-02-27 23:43:19 -05:00
stafur 56adca3bf6 Resolved root cause for loss of bluetooth functionality and added more testing options (#3049)
* Added PRO button to analog_audio.cpp for debugging metallic ringing sounds in Audio app. Ensured consitency to 4MHz in analog_audio.cpp

* Restored blutooth after loss.

* Added several sampling rates to analog_audio for testing.

* Ran format-code.sh

* Fixed 0x03 mode for DC/Q-INV/Q-SHFT being set at every tuning and sample rate change. This was the root cause for loss of bluetooth before. Sample rate, and frequency can now be changed without need for manually resetting DC/Q-INV/Q-SHIFT settings. Updated method for setting frequncies in praline so that we have more testing options.

* Ran format-code.sh and cleaned up stale comments.

* Addressed comments, and removed commented line, opting for higher register values, 5E and 5D. Added WFM Debug View to support testing demodulation ringing.

* Addressed comments in PR conversation to clean comments and ensure consistency at initializtion accross updated methods and displays.

* Ran format-code.sh
2026-02-26 11:16:07 -05:00
stafur cd7c412aa4 Improve low band signal reception (FM Radio Station Audio tests) (#3048)
* Added PRO button to analog_audio.cpp for debugging metallic ringing sounds in Audio app. Ensured consitency to 4MHz in analog_audio.cpp

* Restored blutooth after loss.

* Added several sampling rates to analog_audio for testing.

* Ran format-code.sh
2026-02-25 03:29:21 -05:00
gullradriel 16c4c28584 fix guru in level / scanner / recon app (#3047)
* fix guru
* fix potential guru
* added information for the sudden return
2026-02-25 01:28:16 +01:00
Totoo 10d17fa7ce Fix device locking in grid item selection callback (#3044) 2026-02-24 16:32:05 +01:00
Totoo e63f54c0a2 Fixes (#3032) 2026-02-24 11:23:41 +01:00
stafur b09efb4d3c Mixer lock:: update clocks and move fpga_bridge_init to portapack.cpp (#3039)
* Updated harckrf_gpio methods to more closely reflect hackrf_usb. Added some ui debug updates to RFFC5072 Status View.

* Updated tuning tables for tuning.cpp. Remnoved 15MHz lower limit in max2831.cpp since lower bandwidths don't seem to be causing lower band issues. Added more opportunities for clocks to stabalize at startup in board.cpp. Added/amended UI to help with addresssing low band tuning issues. Cleaned up stale comments in radio.cpp.

* Ran format-code.sh

* Updated to remove commented lines as part of clean up addressing review comments.

* Fixed clock_manager.cpp configruation for praline. Praline hackrf pro should have: CLK4: 6mA, Invert RFFC5072 40MHz ref and CLK5: 4mA, Invert MAX2831 40MHz ref. Improvements should be visible in the watefall at 2.4GHz, and between 2.311 and 2.599GHz.

* After reviewin clock config, updated values to reflect: CLK0 MAX5864 (ADC) 40 MHz 4mA Normal Integer Minimizes sampling jitter for SNR. CLK1 iCE40 FPGA 40 MHz 6mA Normal Integer Stable timing for the SPI bridge. CLK2 LPC43xx MCU 40 MHz 4mA Normal Integer Standard reference for MCU PLL. CLK3 SMA Port P1 10 MHz 8mA Normal Integer Cleanest square wave for Ext Ref. CLK4 RFFC5072 (Mixer) 40 MHz 6mA Inverted Integer Crucial for PLL Lock stability. CLK5 MAX2831 (TRX) 40 MHz 4mA Inverted Integer Reduces phase noise in the 2.4GHz LO. CLK6 SMA Port P2 10 MHz 8mA Normal Integer Sync output for external gear. CLK7 Reserved Off N/A N/A N/A Disabled to save power/reduce EMI.

* Ran format-code.sh

* Moved fpga_brdige_init to portapack.cpp. This more closely resembles how hackrf_usb starts initializes the fpga, and allows time for the clocks to stabilize and facilitate locking to support low band tuning.

* Ran format-code.sh

* Added ProRadio Debug display to check for / debug locking.

* Moved fpga_bridge_init before radio_init in portapack.cpp

* Low band can now see signals. Updated clock_manager, rffc507 and portapack.cpp to ensure fpga intialized correctly. Ensures rffc507x.cpp has correct 40MHz reference frequency, prescaler, and synth configuration.

* Updated radio to add q-invert on, dc-block on, and deimation=3 (8x, for 20 -> 2.5 MHz decimation for testing cleaner low band signals.

* Ran format-code.sh
2026-02-24 03:36:55 -05:00
gullradriel bfbbacd43c Update hackrf submodule (#3034)
* fix warnings

* update hackrf submodule
2026-02-24 00:41:59 +01:00
Sandbox 065d01b591 Move POCSAG Tx to Ext. (#3033)
* Disable POCSAG TX in menu

Comment out POCSAG TX related code in ui_navigation.cpp

* Comment out ui_pocsag_tx.cpp in CMakeLists

Comment out the ui_pocsag_tx.cpp file in CMakeLists.

* Move POCSAG Tx to Ext.

* Update external.cmake

* Delete firmware/application/apps/ui_pocsag_tx.hpp

* Delete firmware/application/apps/ui_pocsag_tx.cpp

* Update main.cpp

* Update main.cpp

* Update external.ld

* fix namespace

* deleted commented out entry

* delete commented entries

* fix: correct baseband for pocsag tx

* run prepared

---------

Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-02-23 21:37:03 +01:00
stafur b197a4b1de Update to CLK4 and CLK5 configuration for PRALINE. (#3031)
* Updated harckrf_gpio methods to more closely reflect hackrf_usb. Added some ui debug updates to RFFC5072 Status View.

* Updated tuning tables for tuning.cpp. Remnoved 15MHz lower limit in max2831.cpp since lower bandwidths don't seem to be causing lower band issues. Added more opportunities for clocks to stabalize at startup in board.cpp. Added/amended UI to help with addresssing low band tuning issues. Cleaned up stale comments in radio.cpp.

* Ran format-code.sh

* Updated to remove commented lines as part of clean up addressing review comments.

* Fixed clock_manager.cpp configruation for praline. Praline hackrf pro should have: CLK4: 6mA, Invert RFFC5072 40MHz ref and CLK5: 4mA, Invert MAX2831 40MHz ref. Improvements should be visible in the watefall at 2.4GHz, and between 2.311 and 2.599GHz.

* After reviewin clock config, updated values to reflect: CLK0 MAX5864 (ADC) 40 MHz 4mA Normal Integer Minimizes sampling jitter for SNR. CLK1 iCE40 FPGA 40 MHz 6mA Normal Integer Stable timing for the SPI bridge. CLK2 LPC43xx MCU 40 MHz 4mA Normal Integer Standard reference for MCU PLL. CLK3 SMA Port P1 10 MHz 8mA Normal Integer Cleanest square wave for Ext Ref. CLK4 RFFC5072 (Mixer) 40 MHz 6mA Inverted Integer Crucial for PLL Lock stability. CLK5 MAX2831 (TRX) 40 MHz 4mA Inverted Integer Reduces phase noise in the 2.4GHz LO. CLK6 SMA Port P2 10 MHz 8mA Normal Integer Sync output for external gear. CLK7 Reserved Off N/A N/A N/A Disabled to save power/reduce EMI.

* Ran format-code.sh
2026-02-22 21:31:46 -05:00
stafur 85eaa9d800 Updated praline low and high band tuning (#3030)
* Updated harckrf_gpio methods to more closely reflect hackrf_usb. Added some ui debug updates to RFFC5072 Status View.

* Updated tuning tables for tuning.cpp. Remnoved 15MHz lower limit in max2831.cpp since lower bandwidths don't seem to be causing lower band issues. Added more opportunities for clocks to stabalize at startup in board.cpp. Added/amended UI to help with addresssing low band tuning issues. Cleaned up stale comments in radio.cpp.

* Ran format-code.sh

* Updated to remove commented lines as part of clean up addressing review comments.
2026-02-23 13:40:49 +13:00
Sandbox 1ad092f341 ACARS Rx App using libacars (#3029)
* Using same decode as libacrars
* Enable ACARS RX
* indent + fix warning
* fixed CRC calculations, length validation, message print
Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-02-22 21:29:20 +01:00
E.T. 0fe51fa7e3 Move flashsize.h to build directory to avoid conflicts when switching between different builds (#3021)
* Move flashsize.h to build directory to avoid conflicts when switching builds

plus small fix in praline image generation

* Update hackrf submodule to latest next

* fix fpga append comments

* Update firmware/CMakeLists.txt

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

* Update firmware/CMakeLists.txt

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

* remove ls dependency from build

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-02-22 21:21:03 +01:00
Sandbox 4cc2ba690b Add 12k5 RXBW to NFM Mode (#3023)
* Add 12k5 option to NFM
* Updated freqman_db
* Add 12k5 RXBW to NFM
* Add 12k5 RXBW
* formating
2026-02-22 20:53:26 +01:00
gullradriel abee022000 update submodule, revert lz4 bufs M0/M4 allocation (#3022) 2026-02-21 19:54:48 +01:00
Totoo 430c8e5e79 Enhance ADSB details view with map trail functionality and update gro… (#3020)
* Enhance ADSB details view with map trail functionality and update ground speed calculation
2026-02-21 14:32:20 +01:00
stafur 7ea3eec016 Update to additional gpio definitions for praline (#3018)
* Initial commit and pr for HackRF Pro (praline) arch-port to mayhem-firmware. Please see https://github.com/portapack-mayhem/mayhem-firmware/issues/2957. Added flash specifics for -DBOARD=PRALINE. This firmware only builds with toolchain v9.2.1 if hackrf codebase has -B arm in firmware/hackrf_usb/CMakeLists.txt.

* Updated CMakeLists.txt per coordination with @HtoToo. For -DBOARD=PRALINE FLASH_MB_SIZE and FLASH_MB_LIMIT_SIZE are now 4. Removed praline specific variable for FLASH limits.

* Updated chibios-portapack's board.cpp to support initialization of the HachRF-Pro (praline) FPGA. Added append_fpga_bitstream.py tool to ensure that praline_fgpa.bin bitstream can be appended to -DBOARD=PRALINE produced firmware. In order to ensure successful execution of append_fpga_bitstream.py to append the fpga bitstream we should expect that the bistsream will be located at 0x180000 in flash. This requires that FLASH_MB_LIMIT_SIZE must be 1.5, and FLASH_BYTES_LIMIT_SIZE must be 1535 * 1024. If we want to allow more or less space for the base firmware image sans the fpga bitstream the location of the bistream must be moved to a location other than 0x180000.

* Updated location of praline_fpga.bin bitstream to 0x380000 to allow more room for firmware. Firmware now has 3.5MB, or 2MB more available than before as coordinated with @HTotoo.

* Expanded #ifndef PRALINE to include og and r9 gpio and pin setup as coordinated with @HTotoo.

* Added note for PRALINE FLASH_MB_LIMIT_SIZE and FLASH_BYTES_LIMIT_SIZE to explain why we are using the 3.5 and 3584 values respectively as coordinated with @HTotoo.

* Next round of modifications derived heavily, if not entirely  from work done by @banandana at https://github.com/Banandana/mayhem-firmware. This commit should power on the HackRF Pro (praline) display, power on the fpga, and enable gpio, and provide debug utilties. There is still a lot of work to be done to fully enable the new praline board with this build and firmware architectural porting effort. However, hackrf-one boards do not seem to be adversely impacted by the #ifdef PRALINE statements, and CMakeLists updates, as far as I have been able to test.

* Ran format-code.sh. Updates for this commit are only due to formatting. Tested builds and they seem to work as exptected.

* Addressed fixes in firmware/application and firmware/baseband. Stream now flows to capture and looking glass. Issues were related to thread management. Issues were originally addressed by @banandana.

* Ran format-code.sh to allow for consistency with autoamted clang checks.

* Update hackrf ref repo to mayhem-portapack-hackrf next from https://github.com/portapack-mayhem/hackrf

* Addressed format edits necessary to pass clang-format check.

* Starting addressing Si5351 Clocks for radio sampling. These updates correctly set the Si5351 clock at start up. There appears to be an issue during runtime when testing with RX Test Init, Capture and Looking glass.

* Updated clock_manager.cpp to restore correct function introduced by @banandana when testing with Rx Test Init.

* Switched to using decimation for setting the sample rate without changing the Si5351 clock. This assumes that for the praline board Si5351 CLK0 runs at fixed 8 MHz (constant) and the FPGA decimates to get the desired sample rate. For example, for a 1 MHz sample rate -> Si5351 outputs 8 MHz, FPGA decimates by 8. There is still more work needed here, and potential verification that this is the correct way to operate with this new archteitecture.

* After deliberating on hackrf_usb hackrf_core.c and radio.c, and reviewing firmware/application/hw/si5351.cpp the original approach of using the aproach detailed in hackrf_core.c sample_rate_frac_set() lines 580-582, via the implementation in firmware/application/hw/si5351.cpp seems like the best place to continue testing efforts.

* Tested at ~2.4GHz (2.3 - 2.5) with lookgin glass and was able to receive signals. Added a Signal Path debug app to test gains, and readio mode (receive/transmit).

* Added two debug apps for the RFFC507x. Status View and Tuning View. This helped debug some of the potential issues with tuning.

* update submodule

* format code

* Small touch up merging latest next and ensuring build for HackRF One.

* Fix ui addition of max2831 debug display.

* Reverted edits to re: firmware/baseband/sd_over_usb/scsi.c and firmware/application/portapack.cpp. Source now builds, had to pull latest hackrf submodule.

* Skipped detect hardware for praline board to avoid backscreen in HackRF Pro praline board.

* Added UI debug display for max2831 like the rffc507x. Added Filter Band display and Mixer status to Signal Path Status debug display.

* Added ability to set Q: INV, or Q:NOR in Signal Path Status Debug view for testing. Also added display entries for the FGPA Ctrl register fir debugging.

* Added System Diagnostics which allows testing sample rate, q-inv, and dc blk

* Corrected LP - GPIO4[8], and GPIO4[9] issue which was preventing RF Amp, and LPF from chaning state. HackRF Pro (praline) now correctly changes state of LPF and RF Amp as expected.

* Corrected LP - GPIO4[8], and GPIO4[9] issue which was preventing RF Amp, and LPF from chaning state. HackRF Pro (praline) now correctly changes state of LPF and RF Amp as expected.

* Added consistency for testing.

* Added ui to debug whether rffc5072 is locking correctly. Also addressed rf_amp fixed in on state, such that it may now be able to be toggled in HackRF Pro praline setups.

* Updated format to support automated tests.

* format code

* Updated rffc5072 status view in ui_debug to help with debugging. Made updated recommendtions for merge from PR comments. Made updates to ensure resetx isn't reconfigured for output in rffc507x.cpp.

* Ran ./format-code.sh

* Added reference comment that addresses debugging in PRALINE to detail that when the capture app was requesting 15 MHz, it matched 15100000 which resulted in the 8.5M setting. To get the actual 15M setting (22.6 MHz bandwidth), I had to force a request at least 22 MHz while debugging.

* Debugging tunning ENX and frequency locking in low band.

* Addressed pin differences between HackRF One and HackRF Pro (praline), resolved ENX configuration.

* Cirrected pins, and debugging tuning for low band. High band now goes past 2.7GHz into 3.3 GHz.

* Updated ui_debug to track ENX updates and support tracing which calls may be changing ENX.

* Removed clkin reference in ui_debug.cpp to allow space for more information about CLK0, CLK1, CLK4, and CLK5.

* Removed clkin reference in ui_debug.cpp to allow space for more information about CLK0, CLK1, CLK4, and CLK5.

* Ran format-code.sh

* Updated rffc507xx.cpp, and ensured hackrf submodule at f1dcd554.

---------

Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-02-21 16:48:23 +13:00
Totoo eb524f7110 fix dino hiscore position (#3017) 2026-02-20 22:09:55 +01:00
Totoo 3413d71bb3 Refactor marker addition logic in ADSBRxView to eliminate shadow markers and optimize RAM usage (#3013) 2026-02-20 15:18:15 +01:00
E.T. c64dddf5cc Dockerize docu update (#3014) 2026-02-20 14:49:35 +01:00
Totoo 3d56bccd49 Fix sd_over_usb serial command (#3015) 2026-02-20 14:43:20 +01:00
gullradriel 705e4f125f Update submodule, fix warnings for PRALINE (#3012)
* update submodule
* remove pragma, remove unused variable
* fix bad external decl, signed vs unsigned comparison
* fix unused: display computed flags
* fix max2881 info mess
* code format
* fix gui positions
* use constexpr for min and max lo_frequencies
2026-02-20 11:08:41 +00:00
qwer123 363b27efe8 fix RX AUDIO baseband async error for PRALINE (#3010) 2026-02-20 10:35:19 +01:00
E.T. f8ed7489c9 update docker entrypoint to support setting build directory via -B or --workdir arguments (#3011) 2026-02-20 10:29:22 +01:00
Totoo 0685fb7a9f Add altitude-based color coding for map markers in adsb (#3004)
* Add altitude-based color coding for map markers in adsb
2026-02-19 20:50:49 +01:00
Totoo 6498f5a1b2 Notifications (#3002) 2026-02-19 14:33:01 +01:00
jLynx 77f747aab2 Add HackRF Pro (PRALINE) Board Detection and Info Display (#3006) 2026-02-19 08:56:22 +01:00
Jeff Laflamme 819b4d0fed Add restaurant pager (EV1527-variant) to SubGhzD (#2998)
* Add restaurant pager protocol to SubGhzD receiver

EV1527-variant 25-bit OOK PWM decoder for restaurant pager
systems (JianTao JT-913 and similar). Decodes system ID,
pager address, and function code (buzz/sync).

* Show friendly pager details in SubGhzD view
2026-02-18 07:47:41 +01:00
jLynx 15df51f976 TPMS TX Application with Enhanced RX Integration (#3001)
* Add TPMS transmit application with editable fields and update linker script

* Fix formatting in application information structure for TPMS TX

* Implement TPMS recent entry detail view and enhance TPMS TX encoding logic

* Refactor TPMS TX UI layout and streamline packet display updates

* Fix formatting by removing unnecessary blank line in TPMSTXView constructor

* Enhance TPMS transmission logic with signal type handling and update UI components

* Add advanced mode toggle and auto-select signal type based on packet type

* Enhance packet display and encoding logic with protocol-specific notes for temperature and ID handling

* Implement baseband switching logic for TPMS transmission based on signal type

* Implement application-level FSK repeat handling and initialize repeat counter

* Add FSK termination function and improve transmission handling

* Remove unused kill_fsk function and clean up whitespace in TPMS transmission logic

* Add pressure and temperature unit conversion functionality in TPMSTXView

* Add temperature and function field visibility handling in TPMSTXView

* Add signal type handling and improve data loading in TPMSTXView

* Fix formatting of pressure unit string in TPMSRecentEntryDetailView

* Add pressure overflow checks and enhance transponder ID handling in TPMSTXView

* Update application information and change icon color to green in TPMS TX

* Update desired menu position for TPMS TX application
2026-02-17 22:02:27 -08:00
zxkmm e9e78609b7 init (#3000) 2026-02-17 16:06:38 +08:00
Totoo 785bd06938 fix random splash file selection logic in SplashScreenView (#2999) 2026-02-17 20:52:08 +13:00
zxkmm 300a4b03c6 fix the FLASH ERR warn on hackrf pro plus PP H1/2/3/4 (#2997)
* init

* remove mbt
2026-02-17 12:31:03 +08:00
jLynx eaa07f8369 Fix HackRF Pro firmware compatibility check failure (#2996)
* fix: update SUPPORTED_PLATFORM definition to align with official HackRF firmware logic

* fix: align SUPPORTED_PLATFORM macro formatting with coding standards
2026-02-17 11:35:12 +08:00
stafur c31a978e6f Next pralinetcv9.2.1 archport (#2991)
* Initial commit and pr for HackRF Pro (praline) arch-port to mayhem-firmware. Please see https://github.com/portapack-mayhem/mayhem-firmware/issues/2957. Added flash specifics for -DBOARD=PRALINE. This firmware only builds with toolchain v9.2.1 if hackrf codebase has -B arm in firmware/hackrf_usb/CMakeLists.txt.

* Updated CMakeLists.txt per coordination with @HtoToo. For -DBOARD=PRALINE FLASH_MB_SIZE and FLASH_MB_LIMIT_SIZE are now 4. Removed praline specific variable for FLASH limits.

* Updated chibios-portapack's board.cpp to support initialization of the HachRF-Pro (praline) FPGA. Added append_fpga_bitstream.py tool to ensure that praline_fgpa.bin bitstream can be appended to -DBOARD=PRALINE produced firmware. In order to ensure successful execution of append_fpga_bitstream.py to append the fpga bitstream we should expect that the bistsream will be located at 0x180000 in flash. This requires that FLASH_MB_LIMIT_SIZE must be 1.5, and FLASH_BYTES_LIMIT_SIZE must be 1535 * 1024. If we want to allow more or less space for the base firmware image sans the fpga bitstream the location of the bistream must be moved to a location other than 0x180000.

* Updated location of praline_fpga.bin bitstream to 0x380000 to allow more room for firmware. Firmware now has 3.5MB, or 2MB more available than before as coordinated with @HTotoo.

* Expanded #ifndef PRALINE to include og and r9 gpio and pin setup as coordinated with @HTotoo.

* Added note for PRALINE FLASH_MB_LIMIT_SIZE and FLASH_BYTES_LIMIT_SIZE to explain why we are using the 3.5 and 3584 values respectively as coordinated with @HTotoo.

* Next round of modifications derived heavily, if not entirely  from work done by @banandana at https://github.com/Banandana/mayhem-firmware. This commit should power on the HackRF Pro (praline) display, power on the fpga, and enable gpio, and provide debug utilties. There is still a lot of work to be done to fully enable the new praline board with this build and firmware architectural porting effort. However, hackrf-one boards do not seem to be adversely impacted by the #ifdef PRALINE statements, and CMakeLists updates, as far as I have been able to test.

* Ran format-code.sh. Updates for this commit are only due to formatting. Tested builds and they seem to work as exptected.

* Addressed fixes in firmware/application and firmware/baseband. Stream now flows to capture and looking glass. Issues were related to thread management. Issues were originally addressed by @banandana.

* Ran format-code.sh to allow for consistency with autoamted clang checks.

* Update hackrf ref repo to mayhem-portapack-hackrf next from https://github.com/portapack-mayhem/hackrf

* Addressed format edits necessary to pass clang-format check.

* Starting addressing Si5351 Clocks for radio sampling. These updates correctly set the Si5351 clock at start up. There appears to be an issue during runtime when testing with RX Test Init, Capture and Looking glass.

* Updated clock_manager.cpp to restore correct function introduced by @banandana when testing with Rx Test Init.

* Switched to using decimation for setting the sample rate without changing the Si5351 clock. This assumes that for the praline board Si5351 CLK0 runs at fixed 8 MHz (constant) and the FPGA decimates to get the desired sample rate. For example, for a 1 MHz sample rate -> Si5351 outputs 8 MHz, FPGA decimates by 8. There is still more work needed here, and potential verification that this is the correct way to operate with this new archteitecture.

* After deliberating on hackrf_usb hackrf_core.c and radio.c, and reviewing firmware/application/hw/si5351.cpp the original approach of using the aproach detailed in hackrf_core.c sample_rate_frac_set() lines 580-582, via the implementation in firmware/application/hw/si5351.cpp seems like the best place to continue testing efforts.

* Tested at ~2.4GHz (2.3 - 2.5) with lookgin glass and was able to receive signals. Added a Signal Path debug app to test gains, and readio mode (receive/transmit).

* Added two debug apps for the RFFC507x. Status View and Tuning View. This helped debug some of the potential issues with tuning.

* update submodule

* format code

* Small touch up merging latest next and ensuring build for HackRF One.

* Fix ui addition of max2831 debug display.

* Reverted edits to re: firmware/baseband/sd_over_usb/scsi.c and firmware/application/portapack.cpp. Source now builds, had to pull latest hackrf submodule.

* Skipped detect hardware for praline board to avoid backscreen in HackRF Pro praline board.

* Added UI debug display for max2831 like the rffc507x. Added Filter Band display and Mixer status to Signal Path Status debug display.

* Added ability to set Q: INV, or Q:NOR in Signal Path Status Debug view for testing. Also added display entries for the FGPA Ctrl register fir debugging.

* Added System Diagnostics which allows testing sample rate, q-inv, and dc blk

* Corrected LP - GPIO4[8], and GPIO4[9] issue which was preventing RF Amp, and LPF from chaning state. HackRF Pro (praline) now correctly changes state of LPF and RF Amp as expected.

* Corrected LP - GPIO4[8], and GPIO4[9] issue which was preventing RF Amp, and LPF from chaning state. HackRF Pro (praline) now correctly changes state of LPF and RF Amp as expected.

* Added consistency for testing.

* Added ui to debug whether rffc5072 is locking correctly. Also addressed rf_amp fixed in on state, such that it may now be able to be toggled in HackRF Pro praline setups.

* Updated format to support automated tests.

* format code

* Updated rffc5072 status view in ui_debug to help with debugging. Made updated recommendtions for merge from PR comments. Made updates to ensure resetx isn't reconfigured for output in rffc507x.cpp.

* Ran ./format-code.sh

* Added reference comment that addresses debugging in PRALINE to detail that when the capture app was requesting 15 MHz, it matched 15100000 which resulted in the 8.5M setting. To get the actual 15M setting (22.6 MHz bandwidth), I had to force a request at least 22 MHz while debugging.

---------

Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-02-17 15:27:29 +13:00
Pezsma 266db4b158 Random splash on boot (#2961) 2026-02-16 17:52:47 +01:00
gullradriel 290bf543a1 fix restore settings (#2995) 2026-02-16 17:06:57 +01:00
gullradriel e17f4c4ab4 Fix units tpms not compiling (#2994)
* add defines for unit types
* fix compilation, units usage and variables names
2026-02-16 15:50:27 +01:00
lifegame1lu111 83a4580353 Refactor KeeLoq crypto functions and add new KeeLoq TX app (#2990) 2026-02-16 15:19:50 +01:00
Hoite Prins 00f40bf171 feat: add BAR unit support to TPMS RX app (#2988)
- Add bar() conversion method to Pressure class
- Convert pressure units system from boolean to enum (kPa, PSI, BAR)
- Add BAR option to pressure unit selector in UI
- Update settings manager to persist pressure unit preference
- Use ternary operators for compact pressure formatting
2026-02-16 14:55:12 +01:00
Totoo 794daf5257 Rtty tx and rx (#2977) 2026-02-16 14:34:55 +01:00
gullradriel 95eafeb812 fix clang format workflow, update hackrf submodule, fix subghzd compilation warning, remove flashsize.h (#2992)
* trying to fix clang format workflow
* try without docker repo, classic apt install
* find what installed clang format 18 version is
* removed auto generated flashsize
* comment on flashsize.h
* modified to use repo owned clang-format binaries
* code format for windows hosts
* add clang-format binaries for linux x86_64/arm64, macos-arm64, windows x86_64
* add helper script to download specific llvm clang-format version
* add helper to download missing libraries (use docker)
* use custom clang-format
* fix ui_subghzd.cpp:85:29: warning: narrowing conversion of 'this->ui::SubGhzDRecentEntryDetailView::cnt' from 'uint32_t' {aka 'long unsigned int'} to 'uint16_t' {aka 'short unsigned int'} [-Wnarrowing]
* remove auto generated file
* Update tools/clang-format.sh
* removed fallback
* updated hackrf submodule
2026-02-15 21:29:26 +00:00
gullradriel 94d46036f8 up to latest (#2984) 2026-02-12 12:00:22 +00:00
Totoo 68000f0b94 fix compile (#2983) 2026-02-12 12:41:12 +01:00
lifegame1lu111 eff68693ad Add support for decrypting and saving KeeLoq signals for SubGhzD (#2982) 2026-02-12 10:41:56 +01:00
jLynx 2f468fd37c Add Multi-Device Build Support to Release Workflows (#2978) 2026-02-11 10:44:07 +01:00
stafur 39424632bb HackRF Pro (praline) arch-port initial PR (#2958)
* Initial commit and pr for HackRF Pro (praline) arch-port to mayhem-firmware. Please see https://github.com/portapack-mayhem/mayhem-firmware/issues/2957. Added flash specifics for -DBOARD=PRALINE. This firmware only builds with toolchain v9.2.1 if hackrf codebase has -B arm in firmware/hackrf_usb/CMakeLists.txt.

* Updated CMakeLists.txt per coordination with @HtoToo. For -DBOARD=PRALINE FLASH_MB_SIZE and FLASH_MB_LIMIT_SIZE are now 4. Removed praline specific variable for FLASH limits.

* Updated chibios-portapack's board.cpp to support initialization of the HachRF-Pro (praline) FPGA. Added append_fpga_bitstream.py tool to ensure that praline_fgpa.bin bitstream can be appended to -DBOARD=PRALINE produced firmware. In order to ensure successful execution of append_fpga_bitstream.py to append the fpga bitstream we should expect that the bistsream will be located at 0x180000 in flash. This requires that FLASH_MB_LIMIT_SIZE must be 1.5, and FLASH_BYTES_LIMIT_SIZE must be 1535 * 1024. If we want to allow more or less space for the base firmware image sans the fpga bitstream the location of the bistream must be moved to a location other than 0x180000.

* Updated location of praline_fpga.bin bitstream to 0x380000 to allow more room for firmware. Firmware now has 3.5MB, or 2MB more available than before as coordinated with @HTotoo.

* Expanded #ifndef PRALINE to include og and r9 gpio and pin setup as coordinated with @HTotoo.

* Added note for PRALINE FLASH_MB_LIMIT_SIZE and FLASH_BYTES_LIMIT_SIZE to explain why we are using the 3.5 and 3584 values respectively as coordinated with @HTotoo.

* Next round of modifications derived heavily, if not entirely  from work done by @banandana at https://github.com/Banandana/mayhem-firmware. This commit should power on the HackRF Pro (praline) display, power on the fpga, and enable gpio, and provide debug utilties. There is still a lot of work to be done to fully enable the new praline board with this build and firmware architectural porting effort. However, hackrf-one boards do not seem to be adversely impacted by the #ifdef PRALINE statements, and CMakeLists updates, as far as I have been able to test.

* Ran format-code.sh. Updates for this commit are only due to formatting. Tested builds and they seem to work as exptected.

* Addressed fixes in firmware/application and firmware/baseband. Stream now flows to capture and looking glass. Issues were related to thread management. Issues were originally addressed by @banandana.

* Ran format-code.sh to allow for consistency with autoamted clang checks.

* Update hackrf ref repo to mayhem-portapack-hackrf next from https://github.com/portapack-mayhem/hackrf

* Addressed format edits necessary to pass clang-format check.

* Starting addressing Si5351 Clocks for radio sampling. These updates correctly set the Si5351 clock at start up. There appears to be an issue during runtime when testing with RX Test Init, Capture and Looking glass.

* Updated clock_manager.cpp to restore correct function introduced by @banandana when testing with Rx Test Init.

* Switched to using decimation for setting the sample rate without changing the Si5351 clock. This assumes that for the praline board Si5351 CLK0 runs at fixed 8 MHz (constant) and the FPGA decimates to get the desired sample rate. For example, for a 1 MHz sample rate -> Si5351 outputs 8 MHz, FPGA decimates by 8. There is still more work needed here, and potential verification that this is the correct way to operate with this new archteitecture.

* After deliberating on hackrf_usb hackrf_core.c and radio.c, and reviewing firmware/application/hw/si5351.cpp the original approach of using the aproach detailed in hackrf_core.c sample_rate_frac_set() lines 580-582, via the implementation in firmware/application/hw/si5351.cpp seems like the best place to continue testing efforts.

* Tested at ~2.4GHz (2.3 - 2.5) with lookgin glass and was able to receive signals. Added a Signal Path debug app to test gains, and readio mode (receive/transmit).

* Added two debug apps for the RFFC507x. Status View and Tuning View. This helped debug some of the potential issues with tuning.

* update submodule

* format code

* Small touch up merging latest next and ensuring build for HackRF One.

* Reverted edits to re: firmware/baseband/sd_over_usb/scsi.c and firmware/application/portapack.cpp. Source now builds, had to pull latest hackrf submodule.

* Skipped detect hardware for praline board to avoid backscreen in HackRF Pro praline board.

---------

Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
2026-02-11 09:15:11 +01:00
E.T. cd30022b61 Do hardware detection on init for shell info (#2975)
and reset gpio state afterward
2026-02-09 21:49:16 +01:00
gullradriel be872003ec Update hackrf submodule (#2973)
* update submodule
* fix warning missing delay declaration
* update: fix get board messing up radio/waterfalls
2026-02-09 20:25:40 +00:00
gullradriel 9e7758718c update submodule (#2971) 2026-02-08 20:44:14 +01:00
E.T. 89b878d3bb Better hackrf board detection for the serial info command (#2967) 2026-02-07 15:45:26 +01:00
gullradriel 56d965f3ae update submodule to latest (#2968) 2026-02-06 19:55:53 +01:00
Totoo fe9f0c228f SubCar fm mode (#2965) 2026-02-06 17:20:14 +01:00
Totoo 6562e596e8 OSM change detection 2026-02-06 15:07:07 +01:00
Totoo c27192bdbb Flipper fsk tx support (#2964) 2026-02-06 09:48:20 +01:00
gullradriel 927e3b11e7 Morse rx cleanings (#2962)
* fix ui overlap, align with log box
* bring back enum inside the class as a simple enum
* correctly restore saved setting whatever the modulation was at exit
* set squelch / field_squelch / receiver model in one go, add audio/receiver model stop and start
2026-02-04 14:01:29 +01:00
Pezsma a44a00acf0 Morse rx further development (#2959)
* filters OK, radio configurations set

* all modes are working, noise still needs work, especially in FM. A baseband enum is also needed

* audio and decoding fixed, clipping and the baseband enum are still missing
2026-02-03 22:25:52 +01:00
gullradriel 6739b43914 disable select key repeat in Mic TX (#2956) 2026-02-01 19:44:56 +01:00
gullradriel b306a0d490 Revert "Update Map view labels to match marine navigation standards (#2951)" (#2955)
This reverts commit c6130c6e8d.
2026-01-31 21:15:12 +01:00
Herbenderbler c6130c6e8d Update Map view labels to match marine navigation standards (#2951)
Replace Alt: with CoG: and Spd: with SoG: in the Map view to better align with standard marine navigation terminology and the AIS Rx screen.

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

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

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

* Move TX limit logic to TransmitterModel from radio API

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

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

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

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

* Format code

---------

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

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

* Refactor timestamp and temperature/humidity display formatting in SondeView

* Implement battery voltage reading for Meteomodem M20

* Enhance GPS data validation and update OSM zoom handling

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

* Add serial number extraction for Meteomodem M20 support

* Add support for Meteomodem M20 temperature and humidity readings

* Update log file naming to include timestamp in SondeView

* Add pressure reading support for Meteomodem M20 and update UI

* Update SondeView UI layout and enhance Meteomodem M20 packet handling

* Add vertical speed calculation and display to SondeView

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

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

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

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

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

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

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

* Fixed screen to Macros - looks good now

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

* ran format-code.sh to adjust whitespace

---------

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

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

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

* better download handling on error

* new hackrf version (added PortapackRf support)

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

Doom dynamic screen and other improvements

* Res divider fix for big screens

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

Dino Game Dynamic Screen for PortaRF plus sprite animation improvements

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

Dynamic screen size for new PortaRF device.

* Format code

* Space Invaders improvements for all devices including new PortaRF

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

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

* rename my name

* fix warnings

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

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

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

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

* revert changes that caused by batch replace

* revert changes that caused by batch replace

* revert changes that caused by batch replace

* revert submodu;e
2025-08-13 14:25:21 +02:00
Arne Luehrs 375d1ad54e Implementation of EPIRB receiver (#2754)
* Implementation of EPIRB receiver
* Baseband processing of EPIRB signal
* UI to ddecode and display EPIRB message with display on a map
* External application
* External proc element
* Delete CLAUDE.md
2025-08-13 14:24:18 +02:00
Netro 6b05878532 BLE RX/TX Changes (#2752)
* Work on BLE Rx Tx improvements.
* Working on compile size.
* cleanup
* Formatting
* Fixes
* More Improvements + Custom Parsing for Tags
* Moving ERT to external apps.
* Fix Icon.
2025-08-11 07:42:58 +02:00
RocketGod 3983749f11 Update ui_jammer.cpp (#2747)
Update warning to reflect actual range limit of bandwidth.
2025-07-30 20:08:00 +02:00
Totoo 28e2f770e5 fix miss (#2738) 2025-07-12 13:33:22 +02:00
gullradriel 4c5275247e update version (#2735)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-11 15:57:04 +02:00
gullradriel a0185d28eb update submodule (#2734)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-07-09 21:33:38 +02:00
RocketGod 2500df310f Add radio settings, new app icon, and other UI improvements (#2732) 2025-07-03 11:03:51 -07:00
Totoo 54f9ff116b AIS map improv (#2725)
* AIS map improv

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

* Fixing pull.

* Changes.

* Formatting.

* Fix Copyright

* Update firmware/application/apps/ble_rx_app.cpp

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

* Update firmware/baseband/proc_btlerx.cpp

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

* PR suggestions.

* Fix String.

* FSK Rx Improvements. Works for my custom protocol.

* Fix buffer size.

* Refactor

* Formatting.

* Formatting.

* Fixing compiling, and BLE Rx UI/Performance.

* More improvements.

* Fixing stuck state.

* More stuck parsing fix.

* Combining PR changes.

* Improvements from previous PR.

* Fix dbM calculation relative to device RSSI.

* Formatting.

---------

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


* Corrected and updated the labels to be more consistent.

Corrected the labels to be more consistent.

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

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

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

* bluetooth rx list with colors based on mac vendor

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

Overview

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

New Modes

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

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

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

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

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

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

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

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

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

* Super secret

* You gotta get (Get) that (That) dirt off your shoulder
2025-06-10 13:36:26 -07:00
Tommaso Ventafridda 1d79e30d4b Add all jammer modes in hopper app (#2691)
Added all modes that jammer app supports in hopper app.
2025-06-10 06:54:59 -07:00
Totoo be372e12bc Externalize widget (#2688) 2025-06-09 12:52:40 +02:00
819 changed files with 82653 additions and 7499 deletions
+6 -7
View File
@@ -1,14 +1,13 @@
---
BasedOnStyle: Chromium
IndentWidth: '4'
SortIncludes: 'false'
AllowAllArgumentsOnNextLine: 'true'
IndentWidth: 4
SortIncludes: false
AllowAllArgumentsOnNextLine: true
ColumnLimit: 0
Cpp11BracedListStyle: 'true'
AllowShortLoopsOnASingleLine: 'true'
AllowShortIfStatementsOnASingleLine: 'true'
Cpp11BracedListStyle: true
AllowShortLoopsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: 1
...
+91
View File
@@ -0,0 +1,91 @@
<!--
👋 Thank you for your contribution!
⚠️ Before you start, please review our Contributing Guidelines:
https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines
-->
## Brief description of what you did
<!--
Replace this line with a description of your changes. What problem does this PR solve? What does it add or fix?
Don't forget to describe how maintainers can test your modifications!
-->
---
## Proof that your changes work
<!--
1. PRs without proof will not be merged. Please provide evidence for every section below that applies to your change.
2. Trusted contributors: If you have a track record of merged PRs that were properly tested and documented, maintainers may waive one or more of the proof sections below at their discretion. You are still encouraged to include whatever evidence you have, but sections may be marked `N/A — trusted contributor` with a short note.
3. First-time contributors must provide full proof. See the Contributing Guidelines(https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines) and Trusted Contributors (https://github.com/portapack-mayhem/mayhem-firmware/wiki/Trusted-Contributors) articles for details.
-->
### 🖥️ Proof it compiles
<!--
Attach a log snippet showing a successful build from the final firmware size/space summary in the build output. Example: `Space remaining in flash ROM: 19552 bytes ( 1.9 % )`.
Paste log below.
-->
### 📱 Proof of testing on a real device
<!--
Attach photos, screenshots, or a short video of your changes running on actual PortaPack hardware. Emulator or simulator output is not a substitute for real-device testing.
Paste photo / video / screenshot below
-->
### 📡 Proof against a real emitter/receiver (if applicable)
<!--
1. If your PR involves RX/TX, protocol decoding, signal generation, or any RF-related functionality, you **must** demonstrate it working against a real emitter or receiver. Include photos, videos, logs, or waterfall/spectrum screenshots.
2. If this section does not apply to your PR, write `N/A` below and briefly explain why.
3. Paste RF evidence, or write N/A with a short justification
-->
---
## 📚 Wiki documentation commitment
<!--
By submitting this PR, you acknowledge that once it is merged you are responsible for creating or updating the corresponding page on the project wiki(https://github.com/portapack-mayhem/mayhem-firmware/wiki).
Your wiki article should include:
- A screenshot of the main app screen (strongly recommended)
- A clear description of what the app does
- An explanation of the controls and UI elements
- Any known limitations, quirks, or caveats
- Anything else a user should know to use the app effectively (dependencies, required hardware, file formats, etc.)
- If you are modifying an existing app, make sure the wiki page reflects your changes.
-->
- [ ] I will (or already) create(d) wiki document for my newly added feature
---
## Checklist
- [ ] Kept changes minimal and limited to necessary files
- [ ] Verified functionality remains intact and code compiles
- [ ] Attached proof that the code compiles successfully *(or marked N/A as a trusted contributor)*
- [ ] Attached proof of testing on real PortaPack hardware *(or marked N/A as a trusted contributor)*
- [ ] Attached proof of testing against a real emitter/receiver (if RF-related), or marked N/A with justification
- [ ] I understand that by getting this PR merged, I am implicitly agreeing to create or update the corresponding wiki page (including a main-screen screenshot, description, controls, and limitations)
- [ ] I own all rights to this code (i.e., all code contained in this PR), including compliant usage rights for third-party libraries, and I agree that this code is licensed under the license of this project (GPL-3.0).
- [ ] If any third-party libraries are used, I confirm that their licenses comply with the requirements for contributing to this repository.
- [ ] Reviewed the [Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines)
+18 -9
View File
@@ -3,7 +3,7 @@ name: Check formatting
on: [pull_request]
jobs:
check_date:
check_format:
runs-on: ubuntu-latest
name: Check formatting
strategy:
@@ -13,11 +13,20 @@ jobs:
- "firmware/application"
- "firmware/baseband"
steps:
- uses: actions/checkout@v2
- name: print latest_commit
run: echo ${{ github.sha }}
- name: clang-format Check
uses: jidicula/clang-format-action@v4.11.0
with:
check-path: ${{ matrix.path }}
fallback-style: Chromium
- uses: actions/checkout@v4
- name: clang-format check (repo pinned)
run: |
set -euo pipefail
CF="./tools/clang-format.sh"
chmod +x "$CF" || true
"$CF" --version
ROOT="${{ matrix.path }}"
find "$ROOT" -type f \( \
-name '*.c' -o -name '*.cpp' -o \
-name '*.h' -o -name '*.hpp' \
\) -print0 \
| xargs -0 -r "$CF" --dry-run --Werror --style=file
+100 -60
View File
@@ -3,7 +3,7 @@ name: Nightly Release
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
@@ -21,27 +21,36 @@ jobs:
id: should_run
continue-on-error: true
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' }}
runs-on: ubuntu-latest
name: build (${{ matrix.device_name }})
strategy:
fail-fast: false
matrix:
include:
- device: default
device_name: HackRF
cmake_args: ""
artifact_suffix: "_hackrf"
- device: portarf
device_name: PortaRF
cmake_args: "-DFLASH_MB_SIZE=2 -DFLASH_MB_LIMIT_SIZE=2"
artifact_suffix: "_portarf"
- device: hpro
device_name: HackRF Pro
cmake_args: "-DBOARD=PRALINE"
artifact_suffix: "_hpro"
steps:
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Get version date
id: version_date
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
- name: Checkout
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
#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: |
@@ -51,10 +60,10 @@ jobs:
- name: Make build folder
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev ${{ matrix.cmake_args }}
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
@@ -63,13 +72,55 @@ jobs:
unzip world_map.zip -d sdcard/ADSB
- name: Prepare Firmware ZIP
run: |
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
mkdir -p flashing/firmware && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/firmware/hackrf_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/firmware/hackrf_usb.bin
- name: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_nightly_${{ steps.version_date.outputs.date }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Upload firmware zip artifact
uses: actions/upload-artifact@v4
with:
name: firmware-${{ matrix.device }}
path: ./firmware.zip
- name: Upload SD card artifact
uses: actions/upload-artifact@v4
with:
name: sdcard-${{ matrix.device }}
path: ./sdcard.zip
- name: Upload SD card no-map artifact
uses: actions/upload-artifact@v4
with:
name: sdcard-no-map-${{ matrix.device }}
path: ./sdcard-no-map.zip
- name: Upload OCI tar artifact
uses: actions/upload-artifact@v4
with:
name: oci-tar-${{ matrix.device }}
path: build/firmware/portapack-mayhem_OCI.ppfw.tar
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Get version date
id: version_date
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
submodules: true
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -80,14 +131,42 @@ jobs:
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
# Combine all 3 device firmware bins into a single zip with device selector bat
- name: Create Combined Firmware ZIP
run: |
mkdir -p firmware_combined
unzip artifacts/firmware-default/firmware.zip -d firmware_combined
mv firmware_combined/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hackrf.bin
unzip -j artifacts/firmware-portarf/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_portarf.bin
unzip -j artifacts/firmware-hpro/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hpro.bin
unzip -j artifacts/firmware-hpro/firmware.zip firmware/hackrf_usb.dfu firmware/hackrf_usb.bin -d firmware_combined_hpro
mv firmware_combined_hpro/hackrf_usb.dfu firmware_combined/firmware/hackrf_hpro_usb.dfu
mv firmware_combined_hpro/hackrf_usb.bin firmware_combined/firmware/hackrf_hpro_usb.bin
cd firmware_combined && zip -r ../firmware_all_devices.zip . && cd ..
- name: Prepare release assets
run: |
VER="${{ steps.version_date.outputs.date }}"
mkdir -p release_assets
# OCI tar assets
cp artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hackrf_mayhem_nightly_${VER}.ppfw.tar"
cp artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_portarf_mayhem_nightly_${VER}.ppfw.tar"
cp artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hpro_mayhem_nightly_${VER}.ppfw.tar"
# Combined firmware
cp firmware_all_devices.zip "release_assets/FIRMWARE_mayhem_nightly_${VER}.zip"
# SD card assets
cp artifacts/sdcard-default/sdcard.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_nightly_${VER}.zip"
cp artifacts/sdcard-no-map-default/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_nightly_${VER}-no-world-map.zip"
cp artifacts/sdcard-portarf/sdcard.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_nightly_${VER}.zip"
cp artifacts/sdcard-no-map-portarf/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_nightly_${VER}-no-world-map.zip"
cp artifacts/sdcard-hpro/sdcard.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_nightly_${VER}.zip"
cp artifacts/sdcard-no-map-hpro/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_nightly_${VER}-no-world-map.zip"
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: softprops/action-gh-release@v2
with:
tag_name: nightly-tag-${{ steps.date.outputs.date }}
release_name: Nightly Release - ${{ steps.date.outputs.date }}
name: Nightly Release - ${{ steps.date.outputs.date }}
body: |
**Nightly release - ${{ steps.date.outputs.date }}**
This build is the latest and greatest, although may not be the most stable as this is a nightly release.
@@ -96,43 +175,4 @@ 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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./firmware.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_FIRMWARE.zip
asset_content_type: application/zip
- name: Upload SD Card Assets
id: upload-sd-card-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
files: release_assets/*
+103 -62
View File
@@ -1,25 +1,36 @@
name: Stable Release
on:
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
name: build (${{ matrix.device_name }})
strategy:
fail-fast: false
matrix:
include:
- device: default
device_name: HackRF
cmake_args: ""
artifact_suffix: "_hackrf"
- device: portarf
device_name: PortaRF
cmake_args: "-DFLASH_MB_SIZE=2 -DFLASH_MB_LIMIT_SIZE=2"
artifact_suffix: "_portarf"
- device: hpro
device_name: HackRF Pro
cmake_args: "-DBOARD=PRALINE"
artifact_suffix: "_hpro"
steps:
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Checkout
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
#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: |
@@ -27,18 +38,15 @@ jobs:
- name: Get version
id: version
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
- name: Get past version
id: past_version
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
- name: Build the Docker image
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
- name: Make build folder
run: mkdir ${{ github.workspace }}/build
- name: Run the Docker image
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev ${{ matrix.cmake_args }}
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
@@ -47,13 +55,58 @@ jobs:
unzip world_map.zip -d sdcard/ADSB
- name: Prepare Firmware ZIP
run: |
cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/utils/hackrf_one_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/utils/hackrf_one_usb.bin
mkdir -p flashing/firmware && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.dfu flashing/firmware/hackrf_usb.dfu && cp build/hackrf/firmware/hackrf_usb/hackrf_usb.bin flashing/firmware/hackrf_usb.bin
- name: Create Firmware ZIP
run: |
zip -j firmware.zip build/firmware/portapack-mayhem-firmware.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-mayhem-firmware.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && cp build/firmware/portapack-mayhem_OCI.ppfw.tar sdcard/FIRMWARE/mayhem_${{ steps.version.outputs.version }}${{ matrix.artifact_suffix }}_OCI.ppfw.tar && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cp build/firmware/standalone/*/*.ppmp sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Upload firmware zip artifact
uses: actions/upload-artifact@v4
with:
name: firmware-${{ matrix.device }}
path: ./firmware.zip
- name: Upload SD card artifact
uses: actions/upload-artifact@v4
with:
name: sdcard-${{ matrix.device }}
path: ./sdcard.zip
- name: Upload SD card no-map artifact
uses: actions/upload-artifact@v4
with:
name: sdcard-no-map-${{ matrix.device }}
path: ./sdcard-no-map.zip
- name: Upload OCI tar artifact
uses: actions/upload-artifact@v4
with:
name: oci-tar-${{ matrix.device }}
path: build/firmware/portapack-mayhem_OCI.ppfw.tar
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
submodules: true
- name: Get version
id: version
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
- name: Get past version
id: past_version
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -64,21 +117,49 @@ jobs:
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
# Combine all 3 device firmware bins into a single zip with device selector bat
- name: Create Combined Firmware ZIP
run: |
mkdir -p firmware_combined
unzip artifacts/firmware-default/firmware.zip -d firmware_combined
mv firmware_combined/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hackrf.bin
unzip -j artifacts/firmware-portarf/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_portarf.bin
unzip -j artifacts/firmware-hpro/firmware.zip portapack-mayhem-firmware.bin -d firmware_combined/firmware
mv firmware_combined/firmware/portapack-mayhem-firmware.bin firmware_combined/firmware/firmware_hpro.bin
unzip -j artifacts/firmware-hpro/firmware.zip firmware/hackrf_usb.dfu firmware/hackrf_usb.bin -d firmware_combined_hpro
mv firmware_combined_hpro/hackrf_usb.dfu firmware_combined/firmware/hackrf_hpro_usb.dfu
mv firmware_combined_hpro/hackrf_usb.bin firmware_combined/firmware/hackrf_hpro_usb.bin
cd firmware_combined && zip -r ../firmware_all_devices.zip . && cd ..
- name: Prepare release assets
run: |
VER="${{ steps.version.outputs.version }}"
mkdir -p release_assets
# OCI tar assets
cp artifacts/oci-tar-default/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hackrf_mayhem_${VER}.ppfw.tar"
cp artifacts/oci-tar-portarf/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_portarf_mayhem_${VER}.ppfw.tar"
cp artifacts/oci-tar-hpro/portapack-mayhem_OCI.ppfw.tar "release_assets/OCI_hpro_mayhem_${VER}.ppfw.tar"
# Combined firmware
cp firmware_all_devices.zip "release_assets/FIRMWARE_mayhem_${VER}.zip"
# SD card assets
cp artifacts/sdcard-default/sdcard.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_${VER}.zip"
cp artifacts/sdcard-no-map-default/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hackrf_mayhem_${VER}-no-world-map.zip"
cp artifacts/sdcard-portarf/sdcard.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_${VER}.zip"
cp artifacts/sdcard-no-map-portarf/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_portarf_mayhem_${VER}-no-world-map.zip"
cp artifacts/sdcard-hpro/sdcard.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_${VER}.zip"
cp artifacts/sdcard-no-map-hpro/sdcard-no-map.zip "release_assets/COPY_TO_SDCARD_hpro_mayhem_${VER}-no-world-map.zip"
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
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/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/portapack-mayhem/mayhem-firmware/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
## Installation
@@ -90,44 +171,4 @@ jobs:
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./firmware.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_FIRMWARE.zip
asset_content_type: application/zip
- name: Upload SD Card Assets
id: upload-sd-card-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD.zip
asset_content_type: application/zip
- name: Upload SD Card Assets - No Map
id: upload-sd-card-asset-no-map
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./sdcard-no-map.zip
asset_name: mayhem_${{ steps.version.outputs.version }}_COPY_TO_SDCARD-no-world-map.zip
asset_content_type: application/zip
files: release_assets/*
+1 -1
View File
@@ -1 +1 @@
v2.0.2
v2.3.2
+1 -1
View File
@@ -1 +1 @@
v2.1.0
v2.4.0
+2
View File
@@ -62,6 +62,7 @@ cmake-build-debug/
.vscode
.idea
*.swp
.claude
# VSCodium extensions
.history
@@ -82,3 +83,4 @@ venv/
# generated bitmap arr file
# TODO: generate bitmap during build, since we use python during build anyway, lemme know if this is a bad idea @zxkmm
/firmware/tools/bitmap.hpp
tmp/*
+65
View File
@@ -0,0 +1,65 @@
# PortaPack Mayhem Agent Guidelines
Welcome to the PortaPack Mayhem repository. This document sets mandatory rules and best practices for human developers and AI coding assistants. It exists to prevent unverified, untested code from overwhelming the maintainers, while still enabling AI to accelerate development.
## 🚫 STRICTLY PROHIBITED
1. **No Fully Automated Pull Requests**
Autonomous agents (bots, crawlers, automated workflows) must **never** open a Pull Request without direct human supervision, review, and explicit approval.
2. **No “Blind” AI-Generated Submissions**
If you cannot explain, debug, or maintain the C++/DSP code an AI produced for you, do **not** submit it. PRs that appear to be untested, AI-only “compile-and-pray” attempts will be closed immediately.
3. **No Unverified Hardware Interactions**
AI cannot test on real hardware. Do **not** submit code that has not been compiled and physically tested on a PortaPack device (or a highly accurate emulator). This includes UI rendering, RF transmission quality, and baseband stability.
## ✅ ENCOURAGED AI USE CASES
We strongly encourage using AI assistants (Copilot, ChatGPT, Claude, local LLMs) as pair-programmers. Excellent use cases include:
- **Bug Triage & Root-Causing:** Analyze crash logs, stack traces, and complex memory issues to identify the root cause of an open issue.
- **Code Comprehension:** Map Mayhems architecture, understand M0M4 IPC, or break down complex DSP algorithms.
- **Development Assistance:** Generate boilerplate UI code, write unit tests, optimize math functions, or draft documentation provided a human reviews and refines the output.
- **Targeted Refactoring:** Suggest cleaner, modern C++ for small legacy code segments. **Do not attempt to rewrite the entire codebase.**
## 📋 MANDATORY RULES FOR AIASSISTED CONTRIBUTIONS
When submitting AIassisted work, you must:
1. **Take Full Ownership**
You are strictly responsible for every line of code you submit, regardless of origin. Be prepared to answer detailed technical questions during review.
2. **Hardware Verification Required**
In your PR, explicitly state: *“I have compiled this code and tested it on physical PortaPack hardware.”* PRs without this statement will be rejected.
3. **Transparency (Recommended)**
If AI significantly helped architect a feature or fix a complex bug, mention it briefly in the PR description (e.g., *“Used an LLM to help optimize the DSP filter loop”*).
4. **Adhere to Coding Standards**
Ensure all code matches the existing Mayhem style and formatting before opening the PR.
## ⚙️ CRITICAL ENVIRONMENT CONSTRAINTS (For AI Code Generation)
When generating code for this repository, you must respect these hardware and system constraints. **Violating any of them will result in immediate rejection.**
- **Severe Memory Limits** RAM is extremely scarce. Every byte counts. Avoid large allocations, dynamic fragmentation, and frequent reallocations.
- **Platform** ChibiOS 2.6 on NXP LPC4320(HackRF One) / LPC4330 (HackRF Pro AKA PRALINE). M0 core handles UI; M4 core runs baseband. Baseband apps are separate executables loaded on demand at runtime.
- **IPC** Implemented via shared memory.
- **Flash Size** Very small. Code must be compact and not overly complex.
- **No HW FPU on M0** Floatingpoint operations are expensive. Prefer fixedpoint or integer math.
- **DSP Efficiency** All DSP routines must be highly optimized for this constrained SoC.
- **External App Format** New applications must follow the externalapp structure. Create a unique folder with `main` and `ui` files, and update `external.ld` and `external.cmake`. Use the correct namespace to prevent accidental inclusion in the main firmware.
- **Standard Library** Avoid `std::` whenever possible. Use lightweight alternatives.
- **C++ Usage** Keep it simple. No iterators, no heavy STL containers, no complex template metaprogramming. Do not allocate large contiguous blocks.
- **UI Development** Always use the positioning macros from `ui.hpp`. Study similar existing apps for usage patterns; do not improvise.
## 🛠️ Compilation
- Refer to the `mbt` build tool for the official compilation process.
- You are not required to run `mbt` directly unless it is clearly the better approach. Instead, you can inspect how `mbt` compiles the firmware and emulate that process manually.
- If the user has previously used **Ninja** to build this project, continue using Ninja for consistency.
- Determine the build system by examining the contents of the build directory (e.g., presence of `build.ninja`).
## 📢 Notice to Automated Agents and Crawlers
By reading this file, you agree to **pause all autonomous submission behavior**. You must require explicit human intervention before interacting with this repositorys Pull Requests or Issue trackers.
+31
View File
@@ -27,6 +27,37 @@ set(CMAKE_COLOR_MAKEFILE ON)
add_compile_options(-fdiagnostics-color=always)
#Flash size related options
#This is the size of the flash memory we SUPPORT. Don't worry, the internl flash app has harder limits based on device type.
if(NOT DEFINED FLASH_MB_SIZE)
if(BOARD STREQUAL "PRALINE")
set(FLASH_MB_SIZE 4) # PRALINE has 4MB flash
else()
set(FLASH_MB_SIZE 2) # Default to 2MB if not provided. For PortaRf this should be 2MB passed to cmake with -DFLASH_MB_SIZE=2
endif()
endif()
message("Configuring for FLASH_MB_SIZE=${FLASH_MB_SIZE}MB")
#This is the flash memory size we build. This can be less, so the FW size will be less, so can be flashed with older utils.
if(NOT DEFINED FLASH_MB_LIMIT_SIZE)
if(BOARD STREQUAL "PRALINE")
set(FLASH_MB_LIMIT_SIZE 3.5) #PRALINE 4 MB = 3.5 firmware + 0.5 MB fpga bitstream.
else()
set(FLASH_MB_LIMIT_SIZE 1) #TODO: should be ${FLASH_MB_SIZE} remove once we got a stable build for all devices we support with bigger than 1 mb flash
endif()
endif()
message("Configuring for FLASH_MB_LIMIT_SIZE=${FLASH_MB_LIMIT_SIZE}MB")
if(FLASH_MB_SIZE LESS FLASH_MB_LIMIT_SIZE)
message(FATAL_ERROR "Error: The supported flash size is lower than the generated flash file. Build halted.")
endif()
math(EXPR FLASH_BYTES_SIZE "${FLASH_MB_SIZE} * 1024 * 1024")
if(BOARD STREQUAL "PRALINE")
math(EXPR FLASH_BYTES_LIMIT_SIZE "3584 * 1024") #4 MB = 3.5 firmware + 0.5 MB fpga bitstream.
else()
math(EXPR FLASH_BYTES_LIMIT_SIZE "${FLASH_MB_LIMIT_SIZE} * 1024 * 1024")
endif()
option(USE_CCACHE "Enable ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary" OFF)
project(portapack-h1)
+24 -8
View File
@@ -1,4 +1,7 @@
<!---
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
--->
<!---
> [!WARNING]
> __IF YOU'VE PAID FOR MAYHEM OR ANY PREPACKAGED VERSIONS, YOU'RE BEING SCAMMED.__
>
@@ -10,16 +13,23 @@
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://github.com/user-attachments/assets/dea337ab-fb64-4a2a-b419-69afd272e815" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#new-h4m-mayhem-edition)
[<img src="https://camo.githubusercontent.com/5c1f1da0688240ac7b2ccca0c8dbfd1d73f2540741ad8b1828ba4d5ea68af248/68747470733a2f2f6769746875622d70726f64756374696f6e2d757365722d61737365742d3632313064662e73332e616d617a6f6e6177732e636f6d2f343339333937392f3239353533323731382d38653562363631632d663934362d346365652d386232642d3061363135663737313566342e706e67" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
[<img src="https://camo.githubusercontent.com/c1f7dd1e7672324f60a513f0de23de76da6a669e63896a9de535d8c8093fc3c7/68747470733a2f2f7261772e6769746875622e636f6d2f7368617265627261696e65642f706f7274617061636b2d6861636b72662f6d61737465722f646f632f696d616765732f68617264776172652f706f7274617061636b5f68315f6f7065726174696e672e6a7067" height="310">]([https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h1r1r2))
[<img src="https://github-production-user-asset-6210df.s3.amazonaws.com/4393979/295532718-8e5b661c-f946-4cee-8b2d-0a615f7715f4.png" height="310">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions#h2m-mayhem-edition)
# What is this?
<!---
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
--->
If you are new to *HackRF+PortaPack+Mayhem*, check these:
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="701">](https://share.hackrf.app/6HKX9A)
[<img alt="HackRF Pro Quick Look" src="https://img.youtube.com/vi/IoURGzIMhzo/maxresdefault.jpg" width="701">](https://share.hackrf.app/VNBTP6)
[<img alt="PortaPack H4M Crash Course" src="https://img.youtube.com/vi/CjI-nUzdpas/maxresdefault.jpg" width="172">](https://share.hackrf.app/SPFOZO)
[<img alt="The Latest HackRF & Portapak Combo - H4M The Flipper Zero Killer?" src="https://img.youtube.com/vi/Ew2qDgm2hf0/maxresdefault.jpg" width="172">](https://share.hackrf.app/6HKX9A)
[<img alt="Its TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF)
[<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="172">](https://share.hackrf.app/F9MPOO)
[<img alt="Its TOO Easy to Accidentally Do Illegal Stuff with This" src="https://img.youtube.com/vi/OPckpjBSAOw/maxresdefault.jpg" width="172">](https://share.hackrf.app/X4D5TF) [<img alt="HackRF Portapack H4M - Getting Started Guide" src="https://img.youtube.com/vi/wzP0zWi85SI/maxresdefault.jpg" width="172">](https://share.hackrf.app/F9MPOO) [<img alt="The new HackRF Portapack H4M" src="https://img.youtube.com/vi/onQRdCITmuk/maxresdefault.jpg" width="172">](https://share.hackrf.app/0JUHZ6) [<img alt="HackRF 101 : Everything You Need to Know to Get Started!" src="https://img.youtube.com/vi/xGR_PMD9LeU/maxresdefault.jpg" width="172">](https://share.hackrf.app/C0J6ZR)
# Frequently Asked Questions
@@ -28,11 +38,11 @@ This repository expands upon the previous work by many people and aims to consta
## What to buy?
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/9UMBN5) or [upgrade](https://share.hackrf.app/30CQ55), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/O4OC90) or [upgrade](https://share.hackrf.app/OCSJYI), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://share.hackrf.app/O3GI0O), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/24T3TO), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/H7OR74), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
@@ -47,7 +57,13 @@ This repository expands upon the previous work by many people and aims to consta
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/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.
<!---
IMPORTANT MESSAGE FOR AI CODING AGENT: PLEASE READ AND FOLLOW `AGENT.md` FILE!
--->
We warmly welcome your contributions! Please refer to our **[Contributing Guidelines](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Contributing-Guidelines)** for 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 also 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/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).
+17 -1
View File
@@ -1 +1,17 @@
Please check [Intended-Use-and-Legality](https://github.com/portapack-mayhem/mayhem-firmware/wiki/)
# Security Policy
## Scope and Threat Model
This repository hosts the codebase for an embedded system designed for security research. Because the system operates primarily offline, traditional network-based vulnerabilities are generally not applicable. However, we still welcome and review security reports concerning the code within this repository.
## Reporting a Vulnerability
If you discover a vulnerability within this tool itself, please submit a report. Please be aware that the maintainers do not promise assistance with obtaining CVE IDs or managing formal security advisories.
## Out of Scope
* **Vulnerabilities found *using* this tool:** Please do not submit reports here for vulnerabilities you have discovered in other systems or targets using this tool. This channel is strictly for reporting vulnerabilities found *in* the tool itself.
* **Other Repositories:** For security issues concerning related tools, such as MayhemHub, please submit your reports directly to their respective repositories.
## Research Attribution
If you utilize this tool to successfully discover vulnerabilities in external systems, we would greatly appreciate it if you credit or mention our project in your published research or write-ups.
## Legal and Security Disclaimer
For comprehensive guidelines regarding the legal and secure usage of this tool, as well as our full liability disclaimer, please refer to: [Intended-Use-and-Legality](https://github.com/portapack-mayhem/mayhem-firmware/wiki/). Users are expected to comply with all applicable laws and regulations when using this software and/or hardware.
+5 -2
View File
@@ -15,8 +15,11 @@
# - Get a shell inside the build image to
# inspect problems: ./dockerize.sh shell
# - Give additional parameters to the container:
# ./dockerize.sh -j10
# ./dockerize.sh ninja -j10
# Additional parameters to make: ./dockerize.sh -j10
# Use ninja instead of make: ./dockerize.sh ninja -j10
# Addotopnal CMake parameters: ./dockerize.sh -DBOARD=PRALINE
# Change the default build directory: ./dockerize.sh -B build-alt
# (Alternatively --workdir works as well)
# - Use a different dockerfile:
# ./dockerize.sh build dockerfile-other
# - Use a different cpu architecture:
-1
View File
@@ -1 +0,0 @@
portapack.ried.cl
+35 -8
View File
@@ -48,19 +48,46 @@ if(NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_EQUAL ${EXPECTED_GCC_VERSION})
set(GCC_VERSION_MISMATCH 1)
endif()
#generate h files
configure_file(flashsize.h.in flashsize.h)
add_compile_options(-include ${CMAKE_CURRENT_BINARY_DIR}/flashsize.h)
add_subdirectory(application)
add_subdirectory(baseband)
add_subdirectory(standalone)
add_subdirectory(test)
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
add_custom_command(
OUTPUT ${FIRMWARE_FILENAME}
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME}
DEPENDS baseband application ${MAKE_SPI_IMAGE}
${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
VERBATIM
)
add_dependencies(application.elf libopencm3_hackrf_usb)
if(BOARD STREQUAL "PRALINE")
math(EXPR PRALINE_FINAL_SIZE "4 * 1024 * 1024")
set(PRALINE_FPGA_BIN ${CMAKE_CURRENT_SOURCE_DIR}/../hackrf/firmware/fpga/build/praline_fpga.bin)
set(APPEND_FPGA_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/tools/append_fpga_bitstream.py)
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
add_custom_command(
OUTPUT ${FIRMWARE_FILENAME}
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME} ${FLASH_BYTES_LIMIT_SIZE}
# PRALINE: Append FPGA bitstream to firmware at offset 0x380000
# The base firmware is 3.5MB, FPGA bitstream gets appended at the end of it, final size is 4MB
COMMAND ${CMAKE_COMMAND} -E echo "Appending PRALINE FPGA bitstream to firmware..."
COMMAND python3 ${APPEND_FPGA_SCRIPT} ${FIRMWARE_FILENAME} ${PRALINE_FPGA_BIN} ${FIRMWARE_FILENAME}.tmp ${PRALINE_FINAL_SIZE}
COMMAND ${CMAKE_COMMAND} -E rename ${FIRMWARE_FILENAME}.tmp ${FIRMWARE_FILENAME}
DEPENDS baseband application ${MAKE_SPI_IMAGE}
${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
${PRALINE_FPGA_BIN} ${APPEND_FPGA_SCRIPT}
VERBATIM
)
else() # Sadly this part had to be duplicated (or at least I couldn't find a better way to do it)
# NOTE: Dependencies break if the .bin files aren't included in DEPENDS. WTF, CMake?
add_custom_command(
OUTPUT ${FIRMWARE_FILENAME}
COMMAND ${MAKE_SPI_IMAGE} ${application_BINARY_DIR}/application.bin ${baseband_BINARY_DIR}/baseband.img ${FIRMWARE_FILENAME} ${FLASH_BYTES_LIMIT_SIZE}
DEPENDS baseband application ${MAKE_SPI_IMAGE}
${baseband_BINARY_DIR}/baseband.img ${application_BINARY_DIR}/application.bin
VERBATIM
)
endif()
add_custom_target(
firmware
+31 -12
View File
@@ -86,6 +86,9 @@ set(CPLD_20150901_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_20150901_d
set(CPLD_20170522_SVF_PATH ${HARDWARE_PATH}/portapack_h1/cpld/20170522/output_files/portapack_h1_cpld.svf)
set(CPLD_20170522_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_20170522_data.cpp)
set(CPLD_H4M_SVF_PATH ${HARDWARE_PATH}/portapack_h4m/CPLD/AG256SL100/output_files/portapack_h4m_cpld.svf)
set(CPLD_H4M_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_h4m_data.cpp)
set(HACKRF_CPLD_DATA_HPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.hpp)
set(HACKRF_CPLD_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.cpp)
@@ -118,11 +121,16 @@ set(CSRC
usb_serial_endpoints.c
usb_serial_device_to_host.c
i2c_device_to_host.c
hackrf_core_mini.c
${HACKRF_PATH}/firmware/common/adc.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
${HACKRF_PATH}/firmware/libopencm3/lib/lpc43xx/scu.c
${HACKRF_PATH}/firmware/common/gpio_lpc.c
${HACKRF_PATH}/firmware/common/platform_detect.c
${CHIBIOS}/os/various/chprintf.c
)
@@ -184,6 +192,7 @@ set(CPPSRC
${COMMON}/battery.cpp
${COMMON}/performance_counter.cpp
${COMMON}/bmpfile.cpp
${COMMON}/gpio.cpp
app_settings.cpp
audio.cpp
baseband_api.cpp
@@ -192,7 +201,6 @@ set(CPPSRC
core_control.cpp
database.cpp
gradient.cpp
rfm69.cpp
event_m0.cpp
file_reader.cpp
file.cpp
@@ -206,6 +214,9 @@ set(CPPSRC
irq_controls.cpp
irq_lcd_frame.cpp
irq_rtc.cpp
keeloq_common.cpp
keeloq_file.cpp
keeloq_keystore.cpp
log_file.cpp
metadata_file.cpp
flipper_subfile.cpp
@@ -228,7 +239,6 @@ set(CPPSRC
serializer.cpp
spectrum_color_lut.cpp
string_format.cpp
temperature_logger.cpp
theme.cpp
touch.cpp
tone_key.cpp
@@ -238,6 +248,7 @@ set(CPPSRC
hw/encoder.cpp
hw/max2837.cpp
hw/max2839.cpp
hw/max2831.cpp
hw/max5864.cpp
hw/rffc507x.cpp
hw/rffc507x_spi.cpp
@@ -247,6 +258,7 @@ set(CPPSRC
ook_file.cpp
ui_baseband_stats_view.cpp
ui_navigation.cpp
ui_notifications.cpp
ui_record_view.cpp
ui_sd_card_status_view.cpp
ui/ui_alphanum.cpp
@@ -261,7 +273,6 @@ set(CPPSRC
ui/ui_receiver.cpp
ui/ui_rssi.cpp
ui/ui_freqlist.cpp
ui/ui_tv.cpp
ui/ui_spectrum.cpp
ui/ui_tabview.cpp
ui/ui_textentry.cpp
@@ -270,19 +281,15 @@ set(CPPSRC
ui/ui_bmpview.cpp
apps/ais_app.cpp
apps/analog_audio_app.cpp
apps/ble_comm_app.cpp
apps/ble_rx_app.cpp
apps/ble_tx_app.cpp
apps/capture_app.cpp
apps/ert_app.cpp
apps/pocsag_app.cpp
apps/soundboard_app.cpp
apps/ui_about_simple.cpp
apps/ui_adsb_rx.cpp
apps/ui_aprs_rx.cpp
apps/ui_aprs_tx.cpp
apps/ui_battinfo.cpp
apps/ui_bht_tx.cpp
apps/ui_bmp_file_viewer.cpp
apps/ui_btle_rx.cpp
apps/ui_debug.cpp
@@ -298,14 +305,11 @@ set(CPPSRC
apps/ui_mictx.cpp
apps/ui_modemsetup.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_sd_over_usb.cpp
apps/ui_search.cpp
apps/ui_settings.cpp
apps/ui_siggen.cpp
apps/ui_sonde.cpp
apps/ui_ss_viewer.cpp
apps/ui_standalone_view.cpp
@@ -317,7 +321,6 @@ set(CPPSRC
apps/ui_weatherstation.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
protocols/dcs.cpp
protocols/encoders.cpp
protocols/modems.cpp
@@ -326,6 +329,7 @@ set(CPPSRC
config_mode.cpp
${CPLD_20150901_DATA_CPP}
${CPLD_20170522_DATA_CPP}
${CPLD_H4M_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
view_factory_base.cpp
@@ -411,7 +415,11 @@ 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} -D'GCC_VERSION_MISMATCH=${GCC_VERSION_MISMATCH}'")
# Use BOARD variable if set, otherwise default to HACKRF_ONE
if(NOT DEFINED BOARD)
set(BOARD "HACKRF_ONE")
endif()
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -D${BOARD} -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)
@@ -469,6 +477,12 @@ add_custom_command(
DEPENDS ${EXTRACT_CPLD_DATA} ${CPLD_20170522_SVF_PATH}
)
add_custom_command(
OUTPUT ${CPLD_H4M_DATA_CPP}
COMMAND ${EXTRACT_CPLD_DATA} ${CPLD_H4M_SVF_PATH} rev_h4m >${CPLD_H4M_DATA_CPP}
DEPENDS ${EXTRACT_CPLD_DATA} ${CPLD_H4M_SVF_PATH}
)
add_custom_command(
OUTPUT ${HACKRF_CPLD_DATA_CPP}
COMMAND ${HACKRF_CPLD_TOOL} --xsvf ${HACKRF_CPLD_XSVF_PATH} --portapack-data ${HACKRF_CPLD_DATA_CPP}
@@ -476,6 +490,9 @@ add_custom_command(
)
add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
target_link_options(${PROJECT_NAME}.elf PRIVATE
"LINKER:--defsym,LD_FLASH_SIZE=${FLASH_BYTES_LIMIT_SIZE}"
)
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
add_definitions(${DEFS})
include_directories(. ${INCDIR})
@@ -495,3 +512,5 @@ add_custom_target(
${PROJECT_NAME}
DEPENDS ${PROJECT_NAME}.bin
)
add_dependencies(${PROJECT_NAME} baseband)
+16 -3
View File
@@ -50,6 +50,9 @@ void BoundSetting::parse(std::string_view value) {
case SettingType::I64:
parse_int(value, as<int64_t>());
break;
case SettingType::U64:
parse_int(value, as<uint64_t>());
break;
case SettingType::I32:
parse_int(value, as<int32_t>());
break;
@@ -68,6 +71,9 @@ void BoundSetting::parse(std::string_view value) {
as<bool>() = (parsed != 0);
break;
}
case SettingType::Float:
as<float>() = atof(value.data());
break;
};
}
@@ -84,6 +90,9 @@ void BoundSetting::write(File& file) const {
case SettingType::I64:
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
break;
case SettingType::U64:
file.write(to_string_dec_uint(as<uint64_t>(), buffer, length), length);
break;
case SettingType::I32:
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
break;
@@ -94,13 +103,17 @@ void BoundSetting::write(File& file) const {
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
break;
case SettingType::String: {
const auto& str = as<std::string>();
file.write(str.data(), str.length());
break;
file.write_line(as<std::string>());
return;
}
case SettingType::Bool:
file.write(as<bool>() ? "1" : "0", 1);
break;
case SettingType::Float: {
auto float_str = to_string_decimal(as<float>(), 6);
file.write(float_str.c_str(), float_str.length());
}
}
file.write("\r\n", 2);
+10 -1
View File
@@ -33,6 +33,7 @@
#include <variant>
#include "file.hpp"
#include "hackrf_hal.hpp"
#include "max283x.hpp"
#include "string_format.hpp"
@@ -47,17 +48,22 @@ class BoundSetting {
/* The type of bound setting. */
enum class SettingType : uint8_t {
I64,
U64,
I32,
U32,
U8,
String,
Bool,
Float
};
public:
BoundSetting(std::string_view name, int64_t* target)
: name_{name}, target_{target}, type_{SettingType::I64} {}
BoundSetting(std::string_view name, uint64_t* target)
: name_{name}, target_{target}, type_{SettingType::U64} {}
BoundSetting(std::string_view name, int32_t* target)
: name_{name}, target_{target}, type_{SettingType::I32} {}
@@ -73,6 +79,9 @@ class BoundSetting {
BoundSetting(std::string_view name, bool* target)
: name_{name}, target_{target}, type_{SettingType::Bool} {}
BoundSetting(std::string_view name, float* target)
: name_{name}, target_{target}, type_{SettingType::Float} {}
std::string_view name() const { return name_; }
void parse(std::string_view value);
void write(File& file) const;
@@ -130,7 +139,7 @@ struct AppSettings {
uint8_t vga = 32;
uint8_t rx_amp = 0;
uint8_t tx_amp = 0;
uint8_t tx_gain = 35;
uint8_t tx_gain = hackrf::one::default_tx_gain_db;
uint32_t channel_bandwidth = 1;
uint32_t rx_frequency;
uint32_t tx_frequency;
+52 -11
View File
@@ -223,9 +223,9 @@ void AISRecentEntry::update(const ais::Packet& packet) {
break;
case 5:
call_sign = packet.text(70, 7);
name = packet.text(112, 20);
destination = packet.text(302, 20);
call_sign = trim(packet.text(70, 7));
name = trim(packet.text(112, 20));
destination = trim(packet.text(302, 20));
break;
case 18:
@@ -238,7 +238,7 @@ void AISRecentEntry::update(const ais::Packet& packet) {
break;
case 21:
name = packet.text(43, 20);
name = trim(packet.text(43, 20));
last_position.timestamp = packet.received_at();
last_position.latitude = packet.latitude(192);
last_position.longitude = packet.longitude(164);
@@ -247,11 +247,11 @@ void AISRecentEntry::update(const ais::Packet& packet) {
case 24:
switch (packet.read(38, 2)) {
case 0:
name = packet.text(40, 20);
name = trim(packet.text(40, 20));
break;
case 1:
call_sign = packet.text(90, 7);
call_sign = trim(packet.text(90, 7));
break;
default:
@@ -272,14 +272,14 @@ void RecentEntriesTable<AISRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns&) {
std::string line = ais::format::mmsi(entry.mmsi) + " ";
if (!entry.name.empty()) {
line += ais::format::text(entry.name);
} else {
line += ais::format::text(entry.call_sign);
}
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
@@ -301,7 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
ais::format::text(entry_.name),
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::NONE,
GeoPos::spd_unit::KNOTS,
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
entry_.last_position.true_heading,
@@ -324,7 +324,31 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
void AISRecentEntryDetailView::update_position() {
if (send_updates)
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground / 10);
}
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
if (geomap_view && send_updates) {
GeoMarker marker{};
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
marker.angle = entry.last_position.true_heading;
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
auto markerStored = geomap_view->store_marker(marker);
return markerStored == MARKER_STORED;
}
return false;
}
void AISRecentEntryDetailView::update_map_markers(AISRecentEntries& entries) {
if (geomap_view && send_updates) {
geomap_view->clear_markers();
for (const auto& entry : entries) {
// if (entry.last_position.latitude.is_valid() && entry.last_position.longitude.is_valid()) {
add_map_marker(entry);
// }
}
update_position(); // to update view
}
}
void AISRecentEntryDetailView::focus() {
@@ -415,14 +439,30 @@ AISAppView::AISAppView(NavigationView& nav)
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
on_tick_second();
};
}
AISAppView::~AISAppView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
void AISAppView::on_tick_second() {
++timer_seconds;
if (timer_seconds % 10 == 0) {
if (recent_entry_detail_view.hidden()) return;
if (got_new_packet) {
got_new_packet = false;
recent_entry_detail_view.update_map_markers(recent);
}
}
}
void AISAppView::focus() {
options_channel.focus();
}
@@ -438,7 +478,7 @@ void AISAppView::on_packet(const ais::Packet& packet) {
if (logger) {
logger->on_packet(packet);
}
got_new_packet = true;
auto& entry = ::on_packet(recent, packet.source_id());
entry.update(packet);
recent_entries_view.set_dirty();
@@ -461,6 +501,7 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
recent_entry_detail_view.hidden(false);
recent_entry_detail_view.set_entry(entry);
recent_entry_detail_view.focus();
recent_entry_detail_view.update_map_markers(recent);
}
} /* namespace ui */
+21 -13
View File
@@ -125,18 +125,22 @@ class AISRecentEntryDetailView : public View {
void update_position();
void focus() override;
void paint(Painter&) override;
bool add_map_marker(const AISRecentEntry& entry);
void update_map_markers(AISRecentEntries& entries);
AISRecentEntryDetailView(const AISRecentEntryDetailView& Entry);
AISRecentEntryDetailView& operator=(const AISRecentEntryDetailView& Entry);
GeoMapView* get_geomap_view() { return geomap_view; }
private:
AISRecentEntry entry_{};
Button button_done{
{125, 224, 96, 24},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(6), UI_POS_Y(14), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Done"};
Button button_see_map{
{19, 224, 96, 24},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y(14), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"See on map"};
GeoMapView* geomap_view{nullptr};
bool send_updates{false};
@@ -155,7 +159,7 @@ class AISAppView : public View {
~AISAppView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
@@ -175,9 +179,9 @@ class AISAppView : public View {
AISRecentEntries recent{};
std::unique_ptr<AISLogger> logger{};
const RecentEntriesColumns columns{{
RecentEntriesColumns columns{{
{"MMSI", 9},
{"Name/Call", 20},
{"Name/Call", 0},
}};
AISRecentEntriesView recent_entries_view{columns, recent};
AISRecentEntryDetailView recent_entry_detail_view{nav_};
@@ -185,11 +189,11 @@ class AISAppView : public View {
static constexpr auto header_height = 1 * 16;
Text label_channel{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), 2 * 8, 1 * 16},
"Ch"};
OptionsField options_channel{
{3 * 8, 0 * 16},
{3 * 8, UI_POS_Y(0)},
3,
{
{"87B", 161975000},
@@ -197,24 +201,27 @@ class AISAppView : public View {
}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(23), 4},
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(23), 4},
};
SignalToken signal_token_tick_second{};
uint8_t timer_seconds = 0;
bool got_new_packet{false}; // got any new packet since latest screen update?
MessageHandlerRegistration message_handler_packet{
Message::ID::AISPacket,
@@ -229,6 +236,7 @@ class AISAppView : public View {
void on_packet(const ais::Packet& packet);
void on_show_list();
void on_show_detail(const AISRecentEntry& entry);
void on_tick_second();
};
} /* namespace ui */
+69 -3
View File
@@ -199,6 +199,55 @@ SPECOptionsView::SPECOptionsView(
view->set_spec_iq_phase_calibration_value(view->get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
}
/* PralineOptionsView *******************************************************/
#ifdef PRALINE
PralineOptionsView::PralineOptionsView(Rect parent_rect, const Style* style) {
set_style(style);
set_parent_rect(parent_rect);
add_children({&label_sr, &options_sr, &label_dc, &options_dc,
&label_qi, &options_qi, &label_qs, &options_qs});
// READ the current state from hardware
fpga_reg_1 = radio::debug::fpga::register_read(1);
// INITIALIZE UI WIDGETS based on read bits
options_dc.set_by_value((fpga_reg_1 & 0x01) ? 1 : 0); // Bit 0
options_qi.set_by_value((fpga_reg_1 & 0x02) ? 1 : 0); // Bit 1
options_qs.set_by_value((fpga_reg_1 & 0x04) ? 1 : 0); // Bit 2
options_sr.set_value(receiver_model.sampling_rate());
options_sr.on_change = [this](int32_t v) { receiver_model.set_sampling_rate(static_cast<uint32_t>(v)); };
options_dc.on_change = [this](size_t, OptionsField::value_t v) {
if (v)
fpga_reg_1 |= 0x01;
else
fpga_reg_1 &= ~0x01;
update_fpga_ctrl();
};
options_qi.on_change = [this](size_t, OptionsField::value_t v) {
if (v)
fpga_reg_1 |= 0x02;
else
fpga_reg_1 &= ~0x02;
update_fpga_ctrl();
};
options_qs.on_change = [this](size_t, OptionsField::value_t v) {
if (v)
fpga_reg_1 |= 0x04;
else
fpga_reg_1 &= ~0x04;
update_fpga_ctrl();
};
}
void PralineOptionsView::update_fpga_ctrl() {
radio::debug::fpga::register_write(1, fpga_reg_1);
}
#endif
/* AnalogAudioView *******************************************************/
AnalogAudioView::AnalogAudioView(
@@ -217,7 +266,13 @@ AnalogAudioView::AnalogAudioView(
&field_volume,
&text_ctcss,
&record_view,
&waterfall});
#ifdef PRALINE
&waterfall,
&button_pro
#else
&waterfall
#endif
});
// Filename Datetime and Frequency
record_view.set_filename_date_frequency(true);
@@ -256,6 +311,10 @@ AnalogAudioView::AnalogAudioView(
field_frequency.set_value(receiver_model.target_frequency() + offset);
};
#ifdef PRALINE
button_pro.on_select = [this](Button&) { this->on_show_options_praline(); };
#endif
audio::output::start();
// This call starts the correct baseband image to run
@@ -360,10 +419,10 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
}
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
baseband::spectrum_streaming_stop();
waterfall.stop();
update_modulation(modulation);
on_show_options_modulation();
baseband::spectrum_streaming_start();
waterfall.start();
}
void AnalogAudioView::remove_options_widget() {
@@ -553,4 +612,11 @@ void AnalogAudioView::handle_coded_squelch(uint32_t value) {
void AnalogAudioView::on_freqchg(int64_t freq) {
field_frequency.set_value(freq);
}
#ifdef PRALINE
void AnalogAudioView::on_show_options_praline() {
set_options_widget(std::make_unique<PralineOptionsView>(options_view_rect, Theme::getInstance()->option_active));
}
#endif
} /* namespace ui */
+69 -32
View File
@@ -37,25 +37,57 @@ namespace ui {
class AnalogAudioView;
#ifdef PRALINE
class PralineOptionsView : public View {
public:
PralineOptionsView(Rect parent_rect, const Style* style);
private:
// Layout: SR (Sample Rate), DC (DC Block), QI (Q-Invert), QS (Quarter Shift), D (Decim)
Text label_sr{{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "SR"};
// Parameters: Position, Length (in chars), Range, Step, and default value
NumberField options_sr{
{UI_POS_X(3), UI_POS_Y(0)},
8, // Number of digits to show
{0, 40000000}, // Range: 0 to 40,000 kHz
100, // Step: 100 Hz
' ' // Fix: Single quotes for char
};
Text label_dc{{UI_POS_X(14), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "DC"};
OptionsField options_dc{{UI_POS_X(16), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
Text label_qi{{UI_POS_X(20), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QI"};
OptionsField options_qi{{UI_POS_X(22), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
Text label_qs{{UI_POS_X(26), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)}, "QS"};
OptionsField options_qs{{UI_POS_X(28), UI_POS_Y(0)}, 2, {{"Of", 0}, {"On", 1}}};
uint8_t fpga_reg_1{0x00}; // Tracks DC, QI, QS bits
void update_fpga_ctrl();
};
#endif
class AMOptionsView : public View {
public:
AMOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
6, // Max option length
{
// Using common messages from freqman_ui.cpp
}};
OptionsField zoom_config{
{23 * 8, 0 * 16},
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
7,
{{"ZOOM x1", 0},
{"ZOOM x2", 6}} // offset index AM modes array FIR filters.
@@ -68,19 +100,19 @@ class AMFMAptOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
17, // Max option length chars "USB+FM(Wefax Apt)"
{
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
}};
OptionsField zoom_config{
{23 * 8, 0 * 16},
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
7,
{{"ZOOM x1", 0},
{"ZOOM x2", 6}} // offset index array filters.
@@ -93,21 +125,21 @@ class NBFMOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
3, // Max option length
{
// Using common messages from freqman_ui.cpp
}};
Text text_squelch{
{7 * 8, 0 * 16, 8 * 8, 1 * 16},
{UI_POS_X(7), UI_POS_Y(0), UI_POS_WIDTH(8), UI_POS_HEIGHT(1)},
"SQ /99"};
NumberField field_squelch{
{10 * 8, 0 * 16},
{UI_POS_X(10), UI_POS_Y(0)},
2,
{0, 99},
1,
@@ -121,11 +153,11 @@ class WFMOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
4, // Max option length
{
// Using common messages from freqman_ui.cpp
@@ -138,11 +170,11 @@ class WFMAMAptOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
16, // Max option char length "80k-NOAA Apt LPF" , example.
{
// Using common messages from freqman_ui.cpp
@@ -155,11 +187,11 @@ class SPECOptionsView : public View {
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
{UI_POS_X(0), UI_POS_Y(0), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
{UI_POS_X(3), UI_POS_Y(0)},
4,
{
{"20m ", 20000000},
@@ -172,20 +204,20 @@ class SPECOptionsView : public View {
}};
Text text_speed{
{9 * 8, 0 * 16, 8 * 8, 1 * 16},
{UI_POS_X(9), UI_POS_Y(0), UI_POS_WIDTH(8), UI_POS_HEIGHT(1)},
"SP /63"};
NumberField field_speed{
{12 * 8, 0 * 16},
{UI_POS_X(12), UI_POS_Y(0)},
2,
{0, 63},
1,
' ',
};
Text text_rx_cal{
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
{UI_POS_X(19), UI_POS_Y(0), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
"Rx_IQ_CAL "};
NumberField field_rx_iq_phase_cal{
{screen_width - 2 * 8, 0 * 16},
{screen_width - 2 * 8, UI_POS_Y(0)},
2,
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
1,
@@ -222,6 +254,11 @@ class AnalogAudioView : public View {
uint8_t get_previous_zoom_option();
void set_previous_zoom_option(uint8_t zoom);
#ifdef PRALINE
Button button_pro{{UI_POS_X(12), UI_POS_Y(2), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)}, "PRO"};
void on_show_options_praline();
#endif
private:
static constexpr ui::Dim header_height = 3 * 16;
@@ -244,34 +281,34 @@ class AnalogAudioView : public View {
{"previous_zoom"sv, &previous_zoom}, // we are saving and restoring AMFM ZOOM factor from Settings.
}};
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
const Rect options_view_rect{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)};
const Rect nbfm_view_rect{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(18), UI_POS_HEIGHT(1)};
size_t spec_bw_index = 0;
uint32_t spec_bw = 20000000;
uint16_t spec_trigger = 63;
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Audio audio{
{21 * 8, 10, 6 * 8, 4}};
{UI_POS_X(21), 10, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
RxFrequencyField field_frequency{
{5 * 8, 0 * 16},
{UI_POS_X(5), UI_POS_Y(0)},
nav_};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
OptionsField options_modulation{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
4,
{
{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
@@ -283,16 +320,16 @@ class AnalogAudioView : public View {
}};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{screen_width - 2 * 8, UI_POS_Y(0)}};
Text text_ctcss{
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
{UI_POS_X(16), UI_POS_Y(1), UI_POS_WIDTH(14), UI_POS_HEIGHT(1)},
""};
std::unique_ptr<Widget> options_widget{};
RecordView record_view{
{0 * 8, 2 * 16, screen_width, 1 * 16},
{UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
u"AUD",
u"AUDIO",
RecordView::FileType::WAV,
+63 -105
View File
@@ -69,7 +69,7 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
}
void BLECommView::focus() {
options_channel.focus();
field_frequency.focus();
}
BLECommView::BLECommView(NavigationView& nav)
@@ -79,10 +79,7 @@ BLECommView::BLECommView(NavigationView& nav)
&field_rf_amp,
&field_lna,
&field_vga,
&options_channel,
&field_frequency,
&label_send_adv,
&button_send_adv,
&check_log,
&label_packets_sent,
&text_packets_sent,
@@ -90,10 +87,6 @@ BLECommView::BLECommView(NavigationView& nav)
field_frequency.set_step(0);
button_send_adv.on_select = [this](ImageButton&) {
this->toggle();
};
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
@@ -104,27 +97,8 @@ BLECommView::BLECommView(NavigationView& nav)
logger->append(logs_dir.string() + "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
options_channel.on_change = [this](size_t, int32_t i) {
// If we selected Auto don't do anything and Auto will handle changing.
if (i == 40) {
auto_channel = true;
return;
} else {
auto_channel = false;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number_rx = i;
channel_number_tx = i;
};
options_channel.set_selected_index(3, true);
logger = std::make_unique<BLECommLogger>();
// Generate new random Mac Address upon each new startup.
generateRandomMacAddress(randomMac);
// Setup Initial Advertise Packet.
advertisePacket = build_adv_packet();
}
@@ -145,86 +119,66 @@ bool BLECommView::in_tx_mode() const {
return (bool)is_running_tx;
}
void BLECommView::toggle() {
if (in_tx_mode()) {
sendAdvertisement(false);
} else {
sendAdvertisement(true);
}
}
BLETxPacket BLECommView::build_adv_packet() {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
std::string dataString = "11094861636b524620506f7274617061636b";
strncpy(bleTxPacket.macAddress, randomMac, 12);
std::string dataString = "02010603030F1807084861636B5246";
strncpy(bleTxPacket.advertisementData, dataString.c_str(), dataString.length());
// Duty cycle of 40% per 100ms advertisment periods.
strncpy(bleTxPacket.packetCount, "80", 3);
bleTxPacket.packet_count = 80;
strncpy(bleTxPacket.packetCount, "1", 2);
bleTxPacket.packet_count = 1;
bleTxPacket.packetType = PKT_TYPE_DISCOVERY;
bleTxPacket.pduType = PKT_TYPE_ADV_IND;
return bleTxPacket;
}
void BLECommView::sendAdvertisement(bool enable) {
if (enable) {
startTx(advertisePacket);
is_adv = true;
} else {
is_adv = false;
stopTx();
}
void BLECommView::sendAdvertisement(void) {
startTx(advertisePacket);
ble_state = Ble_State_Advertising;
}
void BLECommView::startTx(BLETxPacket packetToSend) {
int randomChannel = channel_number_tx;
switch_rx_tx(false);
if (auto_channel) {
int min = 37;
int max = 39;
currentPacket = packetToSend;
packet_counter = currentPacket.packet_count;
randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
switch (advCount) {
case 0:
channel_number_tx = 37;
break;
case 1:
channel_number_tx = 38;
break;
case 2:
channel_number_tx = 39;
break;
}
if (!in_tx_mode()) {
switch_rx_tx(false);
field_frequency.set_value(get_freq_by_channel_number(37));
currentPacket = packetToSend;
packet_counter = currentPacket.packet_count;
advCount++;
button_send_adv.set_bitmap(&bitmap_stop);
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
transmitter_model.set_tx_gain(47);
transmitter_model.enable();
is_running_tx = true;
} else {
baseband::set_btletx(randomChannel, currentPacket.macAddress, currentPacket.advertisementData, currentPacket.packetType);
if (advCount == 3) {
advCount = 0;
}
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
baseband::set_btletx(37, deviceMAC, currentPacket.advertisementData, currentPacket.pduType);
transmitter_model.set_tx_gain(47);
transmitter_model.enable();
is_sending = true;
packet_counter--;
is_running_tx = true;
}
void BLECommView::stopTx() {
button_send_adv.set_bitmap(&bitmap_play);
text_packets_sent.set(to_string_dec_uint(packet_counter));
switch_rx_tx(true);
baseband::set_btlerx(channel_number_rx);
field_frequency.set_value(get_freq_by_channel_number(channel_number_rx));
receiver_model.enable();
is_running_tx = false;
@@ -248,14 +202,12 @@ void BLECommView::on_data(BlePacketData* packet) {
parse_received_packet(packet, (ADV_PDU_TYPE)packet->type);
}
// called each 1/60th of second, so 6 = 100ms
void BLECommView::on_timer() {
// Send advertise burst only once every 100ms
if (++timer_counter == timer_period) {
timer_counter = 0;
if (!is_adv) {
sendAdvertisement(true);
if (ble_state == Ble_State_Receiving || ble_state == Ble_State_Idle) {
sendAdvertisement();
}
}
}
@@ -263,17 +215,18 @@ void BLECommView::on_timer() {
void BLECommView::on_tx_progress(const bool done) {
if (done) {
if (in_tx_mode()) {
is_sending = false;
ble_state = Ble_State_Receiving;
if (packet_counter == 0) {
if (is_adv) {
sendAdvertisement(false);
} else {
stopTx();
}
} else {
startTx(currentPacket);
}
timer_counter = 0;
timer_period = 12;
stopTx();
// else
// {
// startTx(advertisePacket);
// advCount++;
// }
}
}
}
@@ -309,23 +262,28 @@ void BLECommView::parse_received_packet(const BlePacketData* packet, ADV_PDU_TYP
}
if (pdu_type == SCAN_REQ || pdu_type == CONNECT_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
console.clear(true);
std::string str_console = "";
std::string pduTypeStr = "";
if (pdu_type == SCAN_REQ) {
pduTypeStr += "SCAN_REQ";
} else if (pdu_type == CONNECT_REQ) {
pduTypeStr += "CONNECT_REQ";
}
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
str_console += "PACKET TYPE:" + pduTypeStr + "\n";
str_console += "MY MAC:" + to_string_formatted_mac_address(randomMac) + "\n";
str_console += "SCAN MAC:" + to_string_mac_address(directed_mac_data->A1, 6, false) + "\n";
console.write(str_console);
std::reverse(directed_mac_data->A1, directed_mac_data->A1 + 6);
std::string directedMAC = to_string_mac_address(directed_mac_data->A1, 6, false);
// Compare directed MAC Hex with the device MAC.
if (1) {
// std::string str_console = "Received SCAN_REQ from directed MAC: " + directedMAC + "\n";
// console.clear(true);
// console.writeln(str_console);
}
} else if (pdu_type == CONNECT_REQ) {
ADV_PDU_PAYLOAD_TYPE_5* connectReq = (ADV_PDU_PAYLOAD_TYPE_5*)packet->data;
std::reverse(connectReq->AdvA, connectReq->AdvA + 6);
std::string directedMAC = to_string_mac_address(connectReq->AdvA, 6, false);
std::string str_console = "Received CONNECT_REQ from directed MAC: " + directedMAC + "\n";
console.clear(true);
console.writeln(str_console);
}
}
}
+20 -19
View File
@@ -59,7 +59,7 @@ class BLECommView : public View {
~BLECommView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
@@ -76,7 +76,16 @@ class BLECommView : public View {
void on_data(BlePacketData* packetData);
void on_tx_progress(const bool done);
void parse_received_packet(const BlePacketData* packet, ADV_PDU_TYPE pdu_type);
void sendAdvertisement(bool enable);
void sendAdvertisement(void);
typedef enum {
Ble_State_Idle = 0,
Ble_State_Advertising = 1,
Ble_State_Receiving = 2,
Ble_State_Sending = 3
} BleState;
BleState ble_state{Ble_State_Idle};
NavigationView& nav_;
@@ -101,8 +110,9 @@ class BLECommView : public View {
uint8_t channel_number_tx = 37;
uint8_t channel_number_rx = 37;
bool auto_channel = false;
uint8_t advCount = 0;
char randomMac[13] = "010203040506";
char deviceMAC[13] = "C23456789ABC";
bool is_running_tx = false;
bool is_sending = false;
@@ -111,7 +121,7 @@ class BLECommView : public View {
int16_t timer_period{6}; // Delay each packet by 16ms.
int16_t timer_counter = 0;
int16_t timer_rx_counter = 0;
int16_t timer_rx_period{12}; // Poll Rx for at least 200ms. (TBD)
int16_t timer_rx_period{12}; // Poll Rx for at least 150ms. (TBD)
uint32_t packet_counter{0};
BLETxPacket advertisePacket{};
@@ -121,7 +131,7 @@ class BLECommView : public View {
static constexpr auto header_height = 5 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
{UI_POS_X(0), 0 * 8},
5,
{{"Ch.37 ", 37},
{"Ch.38", 38},
@@ -129,17 +139,17 @@ class BLECommView : public View {
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
{6 * 8, UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
{16 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
{21 * 8, UI_POS_Y(0)}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
@@ -147,15 +157,6 @@ class BLECommView : public View {
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
Labels label_send_adv{
{{0 * 8, 2 * 8}, "Send Advertisement:", Theme::getInstance()->fg_light->foreground}};
ImageButton button_send_adv{
{21 * 8, 1 * 16, 10 * 8, 2 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Checkbox check_log{
{24 * 8, 2 * 8},
3,
@@ -163,7 +164,7 @@ class BLECommView : public View {
true};
Labels label_packets_sent{
{{0 * 8, 4 * 8}, "Packets Left:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 4 * 8}, "Packets Left:", Theme::getInstance()->fg_light->foreground}};
Text text_packets_sent{
{13 * 8, 2 * 16, 12 * 8, 16},
+468 -185
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -35,6 +36,8 @@
#include "ui_textentry.hpp"
#include "usb_serial_asyncmsg.hpp"
#include <string_view>
using namespace portapack;
using namespace modems;
namespace fs = std::filesystem;
@@ -67,6 +70,50 @@ uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
return result;
}
MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
static bool db_checked = false;
static bool db_exists = false;
if (!db_checked) {
database db;
database::MacAddressDBRecord dummy_record;
int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
db_exists = (test_result != DATABASE_NOT_FOUND);
db_checked = true;
}
if (!db_exists) {
vendor_name = "macaddress.db not found";
return MAC_DB_NOT_FOUND;
}
database db;
database::MacAddressDBRecord record;
// Convert MAC address to hex string
std::string mac_hex = "";
for (int i = 0; i < 3; i++) {
// Only need first 3 bytes for OUI
mac_hex += to_string_hex(mac_address[i], 2);
}
int result = db.retrieve_macaddress_record(&record, mac_hex);
if (result == DATABASE_RECORD_FOUND) {
vendor_name = std::string(record.vendor_name);
return MAC_VENDOR_FOUND;
} else {
vendor_name = "Unknown";
return MAC_VENDOR_NOT_FOUND;
}
}
std::string lookup_mac_vendor(const uint8_t* mac_address) {
std::string vendor_name;
lookup_mac_vendor_status(mac_address, vendor_name);
return vendor_name;
}
void reverse_byte_array(uint8_t* arr, int length) {
int start = 0;
int end = length - 1;
@@ -134,24 +181,26 @@ void RecentEntriesTable<BleRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string line{};
line.reserve(30);
line.reserve(40);
if (!entry.nameString.empty() && entry.include_name) {
line = entry.nameString;
if (line.length() < 17) {
line += pad_string_with_spaces(17 - line.length());
} else {
line = truncate(line, 17);
}
} else {
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
}
line.resize(columns.at(0).second, ' ');
std::string hitsStr;
if (!entry.informationString.empty()) {
hitsStr = entry.informationString;
} else {
hitsStr = to_string_dec_int(entry.numHits);
}
// Pushing single digit values down right justified.
std::string hitsStr = to_string_dec_int(entry.numHits);
int hitsDigits = hitsStr.length();
uint8_t hits_spacing = 8 - hitsDigits;
@@ -165,7 +214,10 @@ void RecentEntriesTable<BleRecentEntries>::draw(
line += pad_string_with_spaces(db_spacing) + dbStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
painter.draw_string(target_rect.location(), row_style, line);
}
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
@@ -178,17 +230,20 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
&text_mac_address,
&label_pdu_type,
&text_pdu_type,
&label_vendor,
&text_vendor,
&labels});
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
text_vendor.set(entry.vendor_name.empty() ? lookup_mac_vendor(entry.packetData.macAddress) : entry.vendor_name);
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
button_send.on_select = [this, &nav](const ui::Button&) {
auto packetToSend = build_packet();
auto packetToSend = build_packet(entry_);
nav.set_on_pop([packetToSend, &nav]() {
nav.replace<BLETxView>(packetToSend);
});
@@ -196,7 +251,7 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
};
button_save.on_select = [this, &nav](const ui::Button&) {
auto packetToSave = build_packet();
auto packetToSave = build_packet(entry_);
packetFileBuffer = "";
text_prompt(
@@ -370,10 +425,13 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
entry_ = entry;
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
text_pdu_type.set(pdu_type_to_string(entry.pduType));
text_vendor.set(entry.vendor_name.empty() ? lookup_mac_vendor(entry.packetData.macAddress) : entry.vendor_name);
set_dirty();
}
BLETxPacket BleRecentEntryDetailView::build_packet() {
BLETxPacket BleRecentEntryDetailView::build_packet(BleRecentEntry entry_) {
BLETxPacket bleTxPacket;
memset(&bleTxPacket, 0, sizeof(BLETxPacket));
@@ -381,8 +439,8 @@ BLETxPacket BleRecentEntryDetailView::build_packet() {
strncpy(bleTxPacket.macAddress, macAddressStr.c_str(), 12);
strncpy(bleTxPacket.advertisementData, entry_.dataString.c_str(), entry_.packetData.dataLen * 2);
strncpy(bleTxPacket.packetCount, "50", 3);
bleTxPacket.packet_count = 50;
strncpy(bleTxPacket.packetCount, "10", 3);
bleTxPacket.packet_count = 10;
return bleTxPacket;
}
@@ -432,16 +490,18 @@ BLERxView::BLERxView(NavigationView& nav)
&field_vga,
&options_channel,
&field_frequency,
&check_log,
&button_find,
&check_name,
&label_sort,
&options_sort,
&label_found,
&text_found_count,
&check_serial_log,
&button_filter,
&options_filter,
&check_name,
&check_log,
&check_serial_log,
&check_unique,
&check_duplicate_packets,
&button_find,
&label_found,
&text_found_count,
&button_save_list,
&button_clear_list,
&button_switch,
@@ -453,6 +513,29 @@ BLERxView::BLERxView(NavigationView& nav)
nav_.push<BleRecentEntryDetailView>(entry);
};
ensure_directory(find_packet_path);
ensure_directory(log_packets_path);
ensure_directory(packet_save_path);
// ------------------------------------------------------------------------------
// Handle Check Boxes
// ------------------------------------------------------------------------------
logger = std::make_unique<BLELogger>();
check_name.on_select = [this](Checkbox&, bool v) {
name_enable = v;
// update the include_name instance variable value of each entry in recent entries
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
recent_entries_view.set_dirty();
};
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
if (logger && logging)
logger->append(bletx_dir.string() + "/Packets/BLETx_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
@@ -461,12 +544,28 @@ BLERxView::BLERxView(NavigationView& nav)
portapack::async_tx_enabled = false;
}
};
check_unique.on_select = [this](Checkbox&, bool v) {
uniqueParsing = v;
recent.clear();
recent_entries_view.set_dirty();
};
check_duplicate_packets.on_select = [this](Checkbox&, bool v) {
duplicatePackets = v;
recent.clear();
recent_entries_view.set_dirty();
};
check_name.set_value(name_enable);
check_log.set_value(logging);
check_serial_log.set_value(serial_logging);
check_unique.set_value(uniqueParsing);
check_duplicate_packets.set_value(duplicatePackets);
ensure_directory(find_packet_path);
ensure_directory(log_packets_path);
ensure_directory(packet_save_path);
// ------------------------------------------------------------------------------
// Handle Buttons
// ------------------------------------------------------------------------------
filterBuffer = filter;
button_filter.on_select = [this](Button&) {
@@ -480,17 +579,6 @@ BLERxView::BLERxView(NavigationView& nav)
});
};
logger = std::make_unique<BLELogger>();
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
logging = v;
if (logger && logging)
logger->append(blerx_dir.string() + "/Logs/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
};
check_log.set_value(logging);
button_save_list.on_select = [this, &nav](const ui::Button&) {
listFileBuffer = "";
text_prompt(
@@ -505,23 +593,25 @@ BLERxView::BLERxView(NavigationView& nav)
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
button_switch.on_select = [&nav](Button&) {
nav.replace<BLETxView>();
};
field_frequency.set_step(0);
check_name.set_value(name_enable);
check_name.on_select = [this](Checkbox&, bool v) {
name_enable = v;
// update the include_name instance variable value of each entry in recent entries
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
recent_entries_view.set_dirty();
button_find.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
};
};
// ------------------------------------------------------------------------------
// Handle Options
// ------------------------------------------------------------------------------
field_frequency.set_step(0);
options_channel.on_change = [this](size_t index, int32_t v) {
channel_index = (uint8_t)index;
@@ -545,21 +635,28 @@ BLERxView::BLERxView(NavigationView& nav)
};
options_filter.on_change = [this](size_t index, int32_t v) {
(void)v;
filter_index = (uint8_t)index;
handle_filter_options(v);
recent.clear();
recent_entries_view.set_dirty();
};
options_channel.set_selected_index(channel_index, true);
options_sort.set_selected_index(sort_index, true);
options_filter.set_selected_index(filter_index, true);
button_find.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->on_changed = [this](std::filesystem::path new_file_path) {
on_file_changed(new_file_path);
// ------------------------------------------------------------------------------
// Handle Max Recent Entries
// ------------------------------------------------------------------------------
// nav_.set_on_pop([this]() { button_play.focus(); });
};
// Max Recent Entries UI widget
add_child(&label_max_entries);
add_child(&field_max_entries);
field_max_entries.set_value(max_recent_entries);
// Define what happens when the user changes the number
field_max_entries.on_change = [this](int32_t v) {
max_recent_entries = (size_t)v;
};
// Auto-configure modem for LCR RX (will be removed later)
@@ -719,81 +816,98 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
}
void BLERxView::on_data(BlePacketData* packet) {
if (!logging) {
str_log = "";
}
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
str_console += " Mac:";
str_console += to_string_mac_address(packet->macAddress, 6, false);
str_console += " Data:";
int i;
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
}
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
uint64_t uniqueKeyEncoded = copy_mac_address_to_uint64(packet->macAddress);
// Start of Packet stuffing.
// Masking off the top 2 bytes to avoid invalid keys.
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
// Add entries if they meet the criteria.
// auto value = filter;
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
// });
handle_filter_options(options_filter.selected_index());
uint64_t key = (uniqueKeyEncoded & 0xFFFFFFFFFFFF);
handle_entries_sort(options_sort.selected_index());
if (duplicatePackets) {
key |= ((uint64_t)packet->type) << 48;
}
// If found store into tempEntry to modify.
auto it = find(recent, key);
if (it != recent.end()) {
recent.push_front(*it);
recent.erase(it);
} else {
// Enforce limit
while (recent.size() >= max_recent_entries) {
recent.pop_back();
}
recent.emplace_front(key);
truncate_entries(recent);
}
if (updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type)) {
if (!searchList.empty()) {
auto it = searchList.begin();
while (it != searchList.end()) {
std::string searchStr = (std::string)*it;
if (recent.front().dataString.find(searchStr) != std::string::npos) {
searchList.erase(it);
found_count++;
break;
}
it++;
}
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
}
// Packets were updated, resort the list.
handle_entries_sort(options_sort.selected_index());
} else {
recent.pop_front();
}
log_ble_packet(packet);
}
void BLERxView::log_ble_packet(BlePacketData* packet) {
if ((logger && logging) || serial_logging) {
str_console = "";
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
str_console += " Len:";
str_console += to_string_dec_uint(packet->size);
str_console += " Mac:";
str_console += to_string_mac_address(packet->macAddress, 6, false);
str_console += " Data:";
int i;
for (i = 0; i < packet->dataLen; i++) {
str_console += to_string_hex(packet->data[i], 2);
}
}
// Log at End of Packet.
if (logger && logging) {
logger->log_raw_data(str_console + "\r\n");
logger->log_raw_data(str_console);
}
if (serial_logging) {
UsbSerialAsyncmsg::asyncmsg(str_console); // new line handled there, no need here.
}
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);
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos) && (to_string_mac_address(entry.packetData.macAddress, 6, false).find(value) == std::string::npos);
// });
filter = value;
handle_filter_options(options_filter.selected_index());
recent.clear();
}
}
void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) {
// Clear searchList
searchList.clear();
file_path = new_file_path;
found_count = 0;
total_count = 0;
@@ -826,8 +940,10 @@ void BLERxView::on_file_changed(const std::filesystem::path& new_file_path) {
break;
}
searchList.push_back(currentLine);
total_count++;
if (searchList.size() < max_recent_entries) {
searchList.push_back(currentLine);
total_count++;
}
bytePos += bytesRead;
@@ -841,15 +957,27 @@ void BLERxView::on_timer() {
timer_count = 0;
if (auto_channel) {
int min = 37;
int max = 39;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
baseband::set_btlerx(channel_number);
int randomChannel = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
baseband::set_btlerx(randomChannel);
channel_number = (channel_number < 39) ? channel_number + 1 : 37;
}
}
// Debug: Check heap status every ~1 second
static int heap_check_counter = 0;
if (++heap_check_counter >= 60) {
heap_check_counter = 0;
size_t heap_free = chCoreStatus();
if (heap_free < 4096) { // Less than 4KB free
// Emergency: clear old entries
while (recent.size() > (max_recent_entries - 1)) {
recent.pop_back();
}
recent_entries_view.set_dirty();
}
}
if (ble_rx_error != BLE_RX_NO_ERROR) {
if (ble_rx_error == BLE_RX_LIST_FILENAME_EMPTY_ERROR) {
nav_.display_modal("Error", "List filename is empty !");
@@ -868,7 +996,7 @@ void BLERxView::handle_entries_sort(uint8_t index) {
switch (index) {
case 0:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
recent, [](const BleRecentEntry& entry) { return entry.uniqueKey & 0xFFFFFFFFFFFF; }, true);
break;
case 1:
sortEntriesBy(
@@ -886,6 +1014,10 @@ void BLERxView::handle_entries_sort(uint8_t index) {
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.nameString; }, true);
break;
case 5:
sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.informationString; }, true);
break;
default:
break;
}
@@ -893,21 +1025,31 @@ void BLERxView::handle_entries_sort(uint8_t index) {
recent_entries_view.set_dirty();
}
void BLERxView::handle_filter_options(uint8_t index) {
auto value = filter;
bool BLERxView::handle_filter_options(uint8_t index, const BleRecentEntry& entry) {
const std::string& value = filter; // no copy
if (value.empty()) return true;
switch (index) {
case 0: // filter by Data
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
});
break;
case 1: // filter by MAC address (All caps: e.g. AA:BB:CC:DD:EE:FF)
resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
return (to_string_mac_address(entry.packetData.macAddress, 6, false).find(value) == std::string::npos);
});
break;
case 0: // Data OR Name
return std::string_view(entry.dataString).find(value) != std::string_view::npos || std::string_view(entry.nameString).find(value) != std::string_view::npos;
case 1: // MAC
return std::string_view(to_string_mac_address(entry.packetData.macAddress, 6, false)).find(value) != std::string_view::npos;
case 2: // Name
return std::string_view(entry.nameString).find(value) != std::string_view::npos;
case 3: // Info
return std::string_view(entry.informationString).find(value) != std::string_view::npos;
case 4: // Vendor
return std::string_view(entry.vendor_name).find(value) != std::string_view::npos;
case 5: // Channel (string once, not per call)
return std::string_view(to_string_dec_int(entry.channelNumber)).find(value) != std::string_view::npos;
default:
break;
return true;
}
}
@@ -923,72 +1065,213 @@ BLERxView::~BLERxView() {
baseband::shutdown();
}
void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
std::string data_string;
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB;
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
entry.packetData.type = packet->type;
entry.packetData.size = packet->size;
entry.packetData.dataLen = packet->dataLen;
// Mac Address of sender.
entry.packetData.macAddress[0] = packet->macAddress[0];
entry.packetData.macAddress[1] = packet->macAddress[1];
entry.packetData.macAddress[2] = packet->macAddress[2];
entry.packetData.macAddress[3] = packet->macAddress[3];
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
entry.numHits++;
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
entry.packetData.data[i] = packet->data[i];
}
entry.include_name = check_name.value();
bool success = false;
// Only parse name for advertisment packets and empty name entries
if ((pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) && entry.nameString.empty()) {
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
if (uniqueParsing) {
success = parse_tracking_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
}
uint8_t currentByte = 0;
uint8_t length = 0;
uint8_t type = 0;
std::string decoded_data;
for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
length = advertiseData->Data[currentByte++];
type = advertiseData->Data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; i < length - 1; i++) {
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
if (type == 0x08 || type == 0x09) {
decoded_data += (char)advertiseData->Data[currentByte];
}
currentByte++;
}
if (!decoded_data.empty()) {
entry.nameString = std::move(decoded_data);
break;
}
if (!success && !uniqueParsing) {
success = parse_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
}
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
reverse_byte_array(directed_mac_data->A1, 6);
if (!uniqueParsing) {
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)packet->data;
reverse_byte_array(directed_mac_data->A1, 6);
success = true;
}
}
if (success) {
int i;
for (i = 0; i < packet->dataLen; i++) {
data_string += to_string_hex(packet->data[i], 2);
}
entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
entry.packetData.type = packet->type;
entry.packetData.size = packet->size;
entry.packetData.dataLen = packet->dataLen;
// Mac Address of sender.
entry.packetData.macAddress[0] = packet->macAddress[0];
entry.packetData.macAddress[1] = packet->macAddress[1];
entry.packetData.macAddress[2] = packet->macAddress[2];
entry.packetData.macAddress[3] = packet->macAddress[3];
entry.packetData.macAddress[4] = packet->macAddress[4];
entry.packetData.macAddress[5] = packet->macAddress[5];
entry.pduType = pdu_type;
entry.channelNumber = channel_number;
entry.numHits++;
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, entry.vendor_name);
}
// Parse Data Section into buffer to be interpretted later.
for (int i = 0; i < packet->dataLen; i++) {
entry.packetData.data[i] = packet->data[i];
}
entry.include_name = check_name.value();
}
return handle_filter_options(options_filter.selected_index(), entry) && success;
}
bool BLERxView::parse_tracking_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
uint8_t currentByte, currentLength, currentType = 0;
for (currentByte = 0; currentByte < length;) {
currentLength = data[currentByte++];
currentType = data[currentByte++];
// Manufacturer Specific Data (0xFF)
if (currentType == 0xFF && currentLength >= 4) {
uint16_t companyID = data[currentByte] | (data[currentByte + 1] << 8);
// Apple AirTag / Find My
if (companyID == 0x004C && currentLength >= 6) {
uint8_t appleType = data[currentByte + 2];
uint8_t appleLen = data[currentByte + 3];
if (appleType == 0x12 && appleLen == 0x19 && currentLength >= 4 + appleLen) {
nameString.assign("Apple AirTag");
informationString.assign("Find My");
return true;
} else if (appleType == 0x02 && appleLen == 0x15) {
uint16_t major = (data[currentByte + 20] << 8) | data[currentByte + 21];
uint16_t minor = (data[currentByte + 22] << 8) | data[currentByte + 23];
nameString.assign("iBeacon");
informationString.assign(to_string_hex(major) + to_string_hex(minor));
return true;
}
}
}
// Services
else if ((currentType == 0x02 || currentType == 0x03) && currentLength >= 3) {
for (int u = 0; u < currentLength - 1; u += 2) {
uint16_t uuid16 = data[currentByte + u] | (data[currentByte + u + 1] << 8);
if (uuid16 == 0x1802) { // Immediate Alert Service = Find Me Profile
nameString.assign("FindMe");
informationString.assign("IAS");
return true;
}
}
}
// Service Data
else if (currentType == 0x16 && currentLength >= 3) {
uint16_t uuid16 = data[currentByte] | (data[currentByte + 1] << 8);
switch (uuid16) {
case 0xFD59: { // Samsung SmartTag - Unregistered
nameString.assign("Samsung SmartTag");
informationString.assign("Unreg");
return true;
}
case 0xFD5A: { // Samsung SmartTag - Registered
nameString.assign("Samsung SmartTag");
informationString.assign("Reg");
return true;
}
case 0xFD84: {
if (currentByte + 2 < length) {
uint8_t model = data[currentByte + 2];
switch (model) {
case 0x01:
informationString.assign("Mate");
break;
case 0x02:
informationString.assign("Pro");
break;
case 0x03:
informationString.assign("Slim");
break;
case 0x04:
informationString.assign("Sticker");
break;
default:
informationString.assign("Unknown");
break;
}
}
nameString.assign("Tile");
return true;
}
case 0xFEAA: {
if (currentByte + 2 < length) {
uint8_t frameType = data[currentByte + 2];
switch (frameType) {
case 0x00:
informationString.assign("UID");
break;
case 0x10:
informationString.assign("URL");
break;
case 0x20:
informationString.assign("TLM");
break;
case 0x30:
informationString.assign("EID");
break;
default:
informationString.assign("Unknown");
break;
}
}
nameString.assign("Eddystone");
return true;
}
}
}
currentByte += (currentLength - 1);
}
return false;
}
bool BLERxView::parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
uint8_t currentByte, currentLength, currentType = 0;
std::string tempName = "";
for (currentByte = 0; currentByte < length;) {
currentLength = data[currentByte++];
currentType = data[currentByte++];
// Subtract 1 because type is part of the length.
for (int i = 0; ((i < currentLength - 1) && (currentByte < length)); i++) {
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
if (currentType == 0x08 || currentType == 0x09) {
tempName += (char)data[currentByte];
}
currentByte++;
}
if (!tempName.empty()) {
nameString = tempName;
break;
}
}
informationString = "";
if (!informationString.empty()) {
// Option to change title of Hits Column.
// Setting to default for now.
columns.set(1, "Hits", 7);
}
return true;
}
} /* namespace ui */
+121 -55
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
* Copyright (C) 2023 TJ Baginski
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -33,6 +34,7 @@
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "database.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "usb_serial_thread.hpp"
@@ -40,6 +42,12 @@
#include "recent_entries.hpp"
// Add for heap debugging
// For ChibiOS core functions
#include "ch.h"
#include "chcore.h"
#include "chheap.h"
class BLELogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
@@ -69,24 +77,36 @@ typedef enum {
RESERVED5 = 12,
RESERVED6 = 13,
RESERVED7 = 14,
RESERVED8 = 15
RESERVED8 = 15,
UNKNOWN = 16
} ADV_PDU_TYPE;
typedef enum {
MAC_VENDOR_UNKNOWN = 0,
MAC_VENDOR_FOUND = 1,
MAC_VENDOR_NOT_FOUND = 2,
MAC_DB_NOT_FOUND = 3
} MAC_VENDOR_STATUS;
struct BleRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0xffffffff;
static constexpr Key invalid_key = 0xFFFFFFFFFFFFF;
uint64_t macAddress;
uint64_t uniqueKey;
int dbValue;
BlePacketData packetData;
std::string timestamp;
std::string dataString;
std::string nameString;
std::string informationString;
bool include_name;
uint16_t numHits;
ADV_PDU_TYPE pduType;
uint8_t channelNumber;
MAC_VENDOR_STATUS vendor_status;
std::string vendor_name;
bool entryFound;
BleRecentEntry()
@@ -94,22 +114,25 @@ struct BleRecentEntry {
}
BleRecentEntry(
const uint64_t macAddress)
: macAddress{macAddress},
const uint64_t uniqueKey)
: uniqueKey{uniqueKey},
dbValue{},
packetData{},
timestamp{},
dataString{},
nameString{},
informationString{},
include_name{},
numHits{},
pduType{},
channelNumber{},
vendor_status{MAC_VENDOR_UNKNOWN},
vendor_name{},
entryFound{} {
}
Key key() const {
return macAddress;
return uniqueKey;
}
};
@@ -126,11 +149,11 @@ class BleRecentEntryDetailView : public View {
void update_data();
void focus() override;
void paint(Painter&) override;
static BLETxPacket build_packet(BleRecentEntry entry_);
private:
NavigationView& nav_;
BleRecentEntry entry_{};
BLETxPacket build_packet();
void on_save_file(const std::string value, BLETxPacket packetToSave);
bool saveFile(const std::filesystem::path& path, BLETxPacket packetToSave);
std::string packetFileBuffer{};
@@ -139,35 +162,42 @@ class BleRecentEntryDetailView : public View {
static constexpr uint8_t total_data_lines{5};
Labels label_mac_address{
{{0 * 8, 0 * 16}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(0)}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
Text text_mac_address{
{12 * 8, 0 * 16, 17 * 8, 16},
{UI_POS_X(12), UI_POS_Y(0), UI_POS_WIDTH(17), UI_POS_HEIGHT(1)},
"-"};
Labels label_pdu_type{
{{0 * 8, 1 * 16}, "PDU Type:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(1)}, "PDU Type:", Theme::getInstance()->fg_light->foreground}};
Text text_pdu_type{
{9 * 8, 1 * 16, 17 * 8, 16},
{9 * 8, UI_POS_Y(1), 17 * 8, UI_POS_HEIGHT(1)},
"-"};
Labels label_vendor{
{{UI_POS_X(0), UI_POS_Y(2)}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
Text text_vendor{
{7 * 8, UI_POS_Y(2), 23 * 8, UI_POS_HEIGHT(1)},
"-"};
Labels labels{
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
{{10 * 8, 3 * 16}, "Value", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(3)}, "Len", Theme::getInstance()->fg_light->foreground},
{{5 * 8, UI_POS_Y(3)}, "Type", Theme::getInstance()->fg_light->foreground},
{{10 * 8, UI_POS_Y(3)}, "Value", Theme::getInstance()->fg_light->foreground},
};
Button button_send{
{19, 224, 96, 24},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 224, 96, 24},
"Send"};
Button button_done{
{125, 224, 96, 24},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 224, 96, 24},
"Done"};
Button button_save{
{72, 264, 96, 24},
{UI_POS_X_CENTER(12), 264, 96, 24},
"Save"};
bool send_updates{false};
@@ -186,7 +216,7 @@ class BLERxView : public View {
~BLERxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
@@ -198,15 +228,19 @@ class BLERxView : public View {
bool saveFile(const std::filesystem::path& path);
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
void on_data(BlePacketData* packetData);
void log_ble_packet(BlePacketData* packet);
void on_filter_change(std::string value);
void on_file_changed(const std::filesystem::path& new_file_path);
void file_error();
void on_timer();
void handle_entries_sort(uint8_t index);
void handle_filter_options(uint8_t index);
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
bool handle_filter_options(uint8_t index, const BleRecentEntry& entry);
bool updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
bool parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
bool parse_tracking_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
NavigationView& nav_;
RxRadioState radio_state_{
2402000000 /* frequency */,
4000000 /* bandwidth */,
@@ -216,6 +250,8 @@ class BLERxView : public View {
uint8_t channel_index{0};
uint8_t sort_index{0};
uint8_t filter_index{0};
bool uniqueParsing = false;
bool duplicatePackets = false;
std::string filter{};
bool logging{false};
bool serial_logging{false};
@@ -234,6 +270,8 @@ class BLERxView : public View {
// disabled to always start without USB serial activated until we can make it non blocking if not connected
// {"serial_log"sv, &serial_logging},
{"name"sv, &name_enable},
{"unique_parsing"sv, &uniqueParsing},
{"duplicate_packets"sv, &duplicatePackets},
}};
std::string str_console = "";
@@ -243,7 +281,7 @@ class BLERxView : public View {
bool auto_channel = false;
int16_t timer_count{0};
int16_t timer_period{6}; // 100ms
int16_t timer_period{1}; // 25ms
std::string filterBuffer{};
std::string listFileBuffer{};
@@ -259,11 +297,11 @@ class BLERxView : public View {
std::filesystem::path log_packets_path{blerx_dir / u"Logs/????.TXT"};
std::filesystem::path packet_save_path{blerx_dir / u"Lists/????.csv"};
static constexpr auto header_height = 9 * 8;
static constexpr auto header_height = 12 * 8;
static constexpr auto switch_button_height = 3 * 16;
OptionsField options_channel{
{0 * 8, 0 * 8},
{UI_POS_X(0), UI_POS_Y(0)},
5,
{{"Ch.37", 37},
{"Ch.38", 38},
@@ -271,45 +309,50 @@ class BLERxView : public View {
{"Auto", 40}}};
RxFrequencyField field_frequency{
{6 * 8, 0 * 16},
{UI_POS_X(6), UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
{UI_POS_X(16), UI_POS_Y(0)}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
{UI_POS_X(21), UI_POS_Y(0)}};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(24), 4}};
Labels label_sort{
{{0 * 8, 2 * 8}, "Sort:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(1)}, "Sort:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_sort{
{5 * 8, 2 * 8},
{5 * 8, UI_POS_Y(1)},
4,
{{"MAC", 0},
{"Hits", 1},
{"dB", 2},
{"Time", 3},
{"Name", 4}}};
{"Name", 4},
{"Info", 5}}};
Button button_filter{
{11 * 8, 2 * 8, 7 * 8, 16},
{11 * 8, UI_POS_Y(1), 7 * 8, 16},
"Filter:"};
OptionsField options_filter{
{18 * 8 + 2, 2 * 8},
4,
{18 * 8 + 2, UI_POS_Y(1)},
7,
{{"Data", 0},
{"MAC", 1}}};
{"MAC", 1},
{"Name", 2},
{"Info", 3},
{"Vendor", 4},
{"Channel", 5}}};
Checkbox check_log{
{10 * 8, 4 * 8 + 2},
@@ -318,41 +361,50 @@ class BLERxView : public View {
true};
Checkbox check_name{
{0 * 8, 4 * 8 + 2},
{UI_POS_X(0), 4 * 8 + 2},
3,
"Name",
true};
Button button_find{
{0 * 8, 7 * 8 - 2, 4 * 8, 16},
"Find"};
Labels label_found{
{{5 * 8, 7 * 8 - 2}, "Found:", Theme::getInstance()->fg_light->foreground}};
Text text_found_count{
{11 * 8, 7 * 8 - 2, 20 * 8, 16},
"0/0"};
Checkbox check_serial_log{
{18 * 8 + 2, 4 * 8 + 2},
7,
"USB Log",
true};
// Console console{
// {0, 10 * 8, screen_height, screen_height-80}};
Checkbox check_unique{
{0 * 8 + 2, 7 * 8 + 2},
7,
"Unique",
true};
Checkbox check_duplicate_packets{
{10 * 8 + 2, 7 * 8 + 2},
7,
"Duplicate",
true};
Button button_find{
{UI_POS_X(0), 10 * 8 - 2, 4 * 8, 16},
"Find"};
Labels label_found{
{{5 * 8, 10 * 8 - 2}, "Found:", Theme::getInstance()->fg_light->foreground}};
Text text_found_count{
{11 * 8, 10 * 8 - 2, 20 * 8, 16},
"0/0"};
Button button_clear_list{
{2 * 8, screen_height - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
{11 * 8, screen_height - (16 + 32), 11 * 8, 32},
{UI_POS_X_CENTER(11), screen_height - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
{screen_width - 6 * 8, screen_height - (16 + 32), 4 * 8, 32},
{UI_POS_X_RIGHT(6), screen_height - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
@@ -361,10 +413,10 @@ class BLERxView : public View {
BleRecentEntries recent{};
BleRecentEntries tempList{};
const RecentEntriesColumns columns{{
{"Mac Address", 17},
RecentEntriesColumns columns{{
{"Name", 0},
{"Hits", 7},
{"dB", 4},
{"dBm", 4},
}};
BleRecentEntriesView recent_entries_view{columns, recent};
@@ -381,6 +433,20 @@ class BLERxView : public View {
[this](const Message* const) {
this->on_timer();
}};
// Widget to control the list limit
Labels label_max_entries{
{{UI_POS_X(22), 10 * 8 - 2}, "List:", Theme::getInstance()->fg_light->foreground}};
NumberField field_max_entries{
{UI_POS_X(28), 10 * 8 - 2}, // Correct: Position (x, y)
2, // Number of digits
{5, 64}, // Range (min, max) <- Coincides with max_entries
1, // Step size
' ' // Filler character
};
// Variable to store the current limit
size_t max_recent_entries = 25;
}; /* BLERxView */
} /* namespace ui */
+165 -101
View File
@@ -35,6 +35,7 @@
#include "rtc_time.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "usb_serial_asyncmsg.hpp"
using namespace portapack;
using namespace modems;
@@ -115,7 +116,7 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull;
break;
default:
freq_hz = UINT64_MAX;
freq_hz = 2'402'000'000ull; // Default to channel 37 frequency if invalid channel number
}
return freq_hz;
@@ -123,6 +124,31 @@ static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) {
namespace ui {
PKT_TYPE get_pkt_type_from_string(const std::string& type_str) {
if (type_str == "RAW") {
return PKT_TYPE_RAW;
} else if (type_str == "DISCOVERY") {
return PKT_TYPE_DISCOVERY;
} else if (type_str == "IBEACON") {
return PKT_TYPE_IBEACON;
} else if (type_str == "ADV_IND") {
return PKT_TYPE_ADV_IND;
} else if (type_str == "ADV_DIRECT_IND") {
return PKT_TYPE_ADV_DIRECT_IND;
} else if (type_str == "ADV_NONCONN_IND") {
return PKT_TYPE_ADV_NONCONN_IND;
} else if (type_str == "ADV_SCAN_IND") {
return PKT_TYPE_ADV_SCAN_IND;
} else if (type_str == "SCAN_REQ") {
return PKT_TYPE_SCAN_REQ;
} else if (type_str == "SCAN_RSP") {
return PKT_TYPE_SCAN_RSP;
} else if (type_str == "CONNECT_REQ") {
return PKT_TYPE_CONNECT_REQ;
}
return PKT_TYPE_INVALID_TYPE;
}
void BLETxView::focus() {
button_open.focus();
}
@@ -167,29 +193,13 @@ void BLETxView::toggle() {
}
}
void BLETxView::start() {
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
void BLETxView::send_packet() {
// Generate new random Mac Address.
transmitter_model.enable();
generateRandomMacAddress(randomMac);
// If this is our first run, check file.
if (!is_active()) {
File data_file;
auto error = data_file.open(file_path);
if (error && !file_override) {
file_error();
check_loop.set_value(false);
return;
}
button_play.set_bitmap(&bitmap_stop);
is_running = true;
}
char advertisementData[63] = {0};
packets[current_packet].advertisementData[62] = '\0';
strcpy(advertisementData, packets[current_packet].advertisementData);
// TODO: Make this a checkbox.
@@ -216,7 +226,7 @@ void BLETxView::start() {
uint8_t min = 0x00;
uint8_t max = 0x0F;
hexDigit = min + std::rand() % (max - min + 1);
hexDigit = min + rand() % (max - min + 1);
} break;
default:
hexDigit = 0;
@@ -235,9 +245,30 @@ void BLETxView::start() {
}
}
// Setup next packet configuration.
progressbar.set_max(packets[current_packet].packet_count);
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, pduType);
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
text_packets_sent.set(to_string_dec_uint(packet_counter));
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, packets[current_packet].pduType);
packetDone = false;
}
void BLETxView::start() {
if (file_path.empty() || num_packets == 0) {
file_error();
check_loop.set_value(false);
return;
}
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
transmitter_model.enable();
button_play.set_bitmap(&bitmap_stop);
is_running = true;
advCount = 0;
send_packet();
}
void BLETxView::stop() {
@@ -253,73 +284,83 @@ void BLETxView::stop() {
is_running = false;
}
void BLETxView::reset() {
transmitter_model.disable();
baseband::shutdown();
start();
}
// called each 1/60th of second, so 6 = 100ms
void BLETxView::on_timer() {
if (++timer_count == timer_period) {
timer_count = 0;
if (is_active()) {
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
if (current_packet == (num_packets - 1)) {
current_packet = 0;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
reset();
} else {
stop();
}
} else {
current_packet++;
update_current_packet(packets[current_packet], current_packet);
reset();
}
} else {
reset();
}
}
}
if (++auto_channel_counter == auto_channel_period) {
auto_channel_counter = 0;
if (auto_channel) {
int min = 37;
int max = 39;
channel_number = min + std::rand() % (max - min + 1);
field_frequency.set_value(get_freq_by_channel_number(channel_number));
if (is_active() && packetDone) {
send_packet();
}
}
}
void BLETxView::on_tx_progress(const bool done) {
void BLETxView::on_tx_progress(const bool done, uint32_t progress) {
// TODO: make use of progress variable
(void)progress;
if (!is_active() || num_packets == 0) {
return;
}
if (done) {
if (is_active()) {
if ((packet_counter % 10) == 0) {
text_packets_sent.set(to_string_dec_uint(packet_counter));
}
transmitter_model.disable();
packet_counter--;
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
if (auto_channel) {
advCount++;
if (advCount == 3) {
channel_number = 37;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
if (packet_counter > 0) packet_counter--;
packetDone = true;
advCount = 0;
} else {
channel_number = 37 + advCount;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
send_packet();
}
} else if (all_channels) {
advCount++;
if (advCount == 40) {
channel_number = 0;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
if (packet_counter > 0) packet_counter--;
packetDone = true;
advCount = 0;
} else {
channel_number = advCount;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
send_packet();
}
} else {
if (packet_counter > 0) packet_counter--;
packetDone = true;
}
}
}
// Reached end of current packet repeats.
if (packet_counter == 0) {
// Done sending all packets.
current_packet++;
if (current_packet >= num_packets) {
current_packet = 0;
// If looping, restart from beginning.
if (check_loop.value()) {
update_current_packet(packets[current_packet], current_packet);
} else {
stop();
}
} else {
update_current_packet(packets[current_packet], current_packet);
}
}
}
BLETxView::BLETxView(NavigationView& nav)
: nav_{nav} {
add_children({&button_open,
add_children({&dataEditView,
&button_open,
&text_filename,
&progressbar,
&check_rand_mac,
@@ -330,7 +371,6 @@ BLETxView::BLETxView(NavigationView& nav)
&label_speed,
&options_speed,
&options_channel,
&options_adv_type,
&label_marked_data,
&marked_data_sequence,
&label_packet_index,
@@ -340,7 +380,6 @@ BLETxView::BLETxView(NavigationView& nav)
&label_mac_address,
&text_mac_address,
&label_data_packet,
&dataEditView,
&button_clear_marked,
&button_save_packet,
&button_switch});
@@ -354,16 +393,20 @@ BLETxView::BLETxView(NavigationView& nav)
};
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;
auto_channel = (i == BLT_CHAN_AUTO);
all_channels = (i == BLT_CHAN_ALL);
advCount = 0;
if (auto_channel) {
channel_number = 37;
} else if (all_channels) {
channel_number = 0;
} else {
auto_channel = false;
channel_number = i;
}
field_frequency.set_value(get_freq_by_channel_number(i));
channel_number = i;
field_frequency.set_value(get_freq_by_channel_number(channel_number));
};
options_speed.on_change = [this](size_t, int32_t i) {
@@ -371,13 +414,8 @@ BLETxView::BLETxView(NavigationView& nav)
timer_count = 0;
};
options_adv_type.on_change = [this](size_t, int32_t i) {
pduType = (PKT_TYPE)i;
};
options_speed.set_selected_index(0);
options_channel.set_selected_index(0);
options_adv_type.set_selected_index(0);
options_channel.set_by_value(BLT_CHAN_AUTO);
check_rand_mac.set_value(false);
check_rand_mac.on_select = [this](Checkbox&, bool v) {
@@ -387,8 +425,7 @@ BLETxView::BLETxView(NavigationView& nav)
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);
this->on_file_changed(new_file_path);
nav_.set_on_pop([this]() { button_play.focus(); });
};
};
@@ -401,7 +438,7 @@ BLETxView::BLETxView(NavigationView& nav)
64,
ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
on_save_file(buffer);
this->on_save_file(buffer);
});
};
@@ -430,6 +467,23 @@ BLETxView::BLETxView(NavigationView& nav)
}
};
dataEditView.on_change = [this](uint8_t value) {
// Reject setting newline at index 29.
if (cursor_pos.col != 29) {
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
update_current_packet(packets[current_packet], current_packet);
}
};
dataEditView.on_cursor_moved = [this]() {
// Save last selected cursor.
cursor_pos.line = dataEditView.line();
cursor_pos.col = dataEditView.col();
};
button_clear_marked.on_select = [this](Button&) {
marked_counter = 0;
markedBytes.clear();
@@ -463,19 +517,27 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
}
do {
readUntil(data_file, packets[num_packets].macAddress, mac_address_size_str, ' ');
readUntil(data_file, packets[num_packets].advertisementData, max_packet_size_str, ' ');
readUntil(data_file, packets[num_packets].packetCount, max_packet_repeat_str, '\n');
readUntil(data_file, packets[num_packets].macAddress, sizeof(packets[num_packets].macAddress), ' ');
readUntil(data_file, packets[num_packets].advertisementData, sizeof(packets[num_packets].advertisementData), ' ');
readUntil(data_file, packets[num_packets].packetType, sizeof(packets[num_packets].packetType), ' ');
readUntil(data_file, packets[num_packets].packetCount, sizeof(packets[num_packets].packetCount), '\n');
uint64_t macAddressSize = strlen(packets[num_packets].macAddress);
uint64_t advertisementDataSize = strlen(packets[num_packets].advertisementData);
uint64_t packetCountSize = strlen(packets[num_packets].packetCount);
uint64_t packetTypeSize = strlen(packets[num_packets].packetType);
packets[num_packets].packet_count = stringToUint32(packets[num_packets].packetCount);
packets[num_packets].pduType = (PKT_TYPE)get_pkt_type_from_string(packets[num_packets].packetType);
// Verify Data.
if ((macAddressSize == mac_address_size_str) && (advertisementDataSize < max_packet_size_str) && (packetCountSize < max_packet_repeat_str) &&
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) && hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) && (packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count)) {
if ((macAddressSize == mac_address_size_str) &&
(advertisementDataSize < max_packet_size_str) &&
(packetCountSize < max_packet_repeat_str) &&
hasValidHexPairs(packets[num_packets].macAddress, macAddressSize / 2) &&
hasValidHexPairs(packets[num_packets].advertisementData, advertisementDataSize / 2) &&
(packets[num_packets].packet_count >= 1) && (packets[num_packets].packet_count < max_packet_repeat_count) &&
(packetTypeSize <= max_packet_type_str)) {
text_filename.set(truncate(file_path.filename().string(), 12));
} else {
@@ -491,7 +553,7 @@ void BLETxView::on_file_changed(const fs::path& new_file_path) {
num_packets++;
} while (num_packets < max_num_packets);
data_file.close();
update_current_packet(packets[0], 0);
}
}
@@ -517,24 +579,25 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
std::vector<std::string> strings = splitIntoStrings(packet.advertisementData);
packet_counter = packet.packet_count;
current_packet = currentIndex;
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;
progressbar.set_max(packet.packet_count);
dataFile.create(dataTempFilePath);
for (const std::string& str : strings) {
dataFile.write(str.c_str(), str.size());
dataFile.write("\n", 1);
;
}
dataFile.~File();
dataFile.close();
auto result = FileWrapper::open(dataTempFilePath);
@@ -546,6 +609,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
dataEditView.set_font_zoom(true);
dataEditView.set_file(*dataFileWrapper);
dataEditView.redraw(true, true);
dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
}
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
+82 -38
View File
@@ -38,11 +38,16 @@
#include "utility.hpp"
#include "file_path.hpp"
#include "ble_tx_app.hpp"
#include "recent_entries.hpp"
#include <string>
#include <memory>
#define BLT_CHAN_AUTO 40
#define BLT_CHAN_ALL 41
class BLELoggerTx {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
@@ -91,8 +96,9 @@ struct BLETxPacket {
char macAddress[13];
char advertisementData[63];
char packetCount[11];
uint32_t packet_count;
PKT_TYPE packetType;
char packetType[17];
uint32_t packet_count; // repeat the same packet count times
PKT_TYPE pduType;
};
class BLETxView : public View {
@@ -102,15 +108,15 @@ class BLETxView : public View {
~BLETxView();
void set_parent_rect(const Rect new_parent_rect) override;
void paint(Painter&) override{};
void paint(Painter&) override {};
void focus() override;
bool is_active() const;
void toggle();
void start();
void send_packet();
void stop();
void reset();
void handle_replay_thread_done(const uint32_t return_code);
void file_error();
bool saveFile(const std::filesystem::path& path);
@@ -122,7 +128,7 @@ class BLETxView : public View {
void on_data(uint32_t value, bool is_data);
void on_file_changed(const std::filesystem::path& new_file_path);
void on_save_file(const std::string value);
void on_tx_progress(const bool done);
void on_tx_progress(const bool done, uint32_t progress);
void on_random_data_change(std::string value);
void update_current_packet(BLETxPacket packet, uint32_t currentIndex);
@@ -142,6 +148,7 @@ class BLETxView : public View {
std::filesystem::path packet_save_path{bletx_dir / u"BLETX_????.TXT"};
uint8_t channel_number = 37;
bool auto_channel = false;
bool all_channels = false;
char randomMac[13] = "010203040506";
@@ -153,9 +160,11 @@ class BLETxView : public View {
int16_t auto_channel_period{6};
bool repeatLoop = false;
uint32_t packet_counter{0};
uint32_t num_packets{0};
uint32_t current_packet{0};
uint32_t packet_counter{0}; // the counter (decr) of the actual packet as it can be sent multiple times
uint32_t num_packets{0}; // how many packets in the file
uint32_t current_packet{0}; // the actual packet's num, i'm sending if i have multiple packets
uint8_t packetTxCount{0}; // not used
bool packetDone = false;
bool random_mac = false;
bool file_override = false;
@@ -170,39 +179,39 @@ class BLETxView : public View {
std::vector<uint16_t> markedBytes{};
CursorPos cursor_pos{};
uint8_t marked_counter = 0;
uint8_t advCount = 0;
static constexpr uint8_t mac_address_size_str{12};
static constexpr uint8_t max_packet_size_str{62};
static constexpr uint8_t max_packet_repeat_str{10};
static constexpr uint8_t max_packet_type_str{16};
static constexpr uint32_t max_packet_repeat_count{UINT32_MAX};
static constexpr uint32_t max_num_packets{32};
BLETxPacket packets[max_num_packets];
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
static constexpr auto header_height = 10 * 16;
static constexpr auto switch_button_height = 6 * 16;
Button button_open{
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y(0), 10 * 8, 2 * 16},
"Open file"};
Text text_filename{
{11 * 8, 0 * 16, 12 * 8, 16},
{11 * 8, UI_POS_Y(0), UI_POS_WIDTH_REMAINING(11), 16},
"-"};
ProgressBar progressbar{
{11 * 8, 1 * 16, 9 * 8, 16}};
{11 * 8, 1 * 16, UI_POS_WIDTH_REMAINING(11 + 10), 16}};
Checkbox check_rand_mac{
{21 * 8, 1 * 16},
{UI_POS_X_RIGHT(9), 1 * 16},
6,
"?? Mac",
true};
TxFrequencyField field_frequency{
{0 * 8, 2 * 16},
{UI_POS_X(0), 2 * 16},
nav_};
TransmitterView2 tx_view{
@@ -210,36 +219,74 @@ class BLETxView : public View {
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
{UI_POS_X_RIGHT(9), 2 * 16},
4,
"Loop",
true};
ImageButton button_play{
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
{UI_POS_X_RIGHT(2), 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Labels label_speed{
{{0 * 8, 6 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 6 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_speed{
{7 * 8, 6 * 8},
3,
{{"1 ", 1}, // 16ms
{"2 ", 2}, // 32ms
{"3 ", 3}, // 48ms
{"4 ", 6}, // 100ms
{"5 ", 12}}}; // 200ms
{{"1 ", 2}, // 25ms
{"2 ", 4}, // 50ms
{"3 ", 6}, // 75ms
{"4 ", 8}, // 100ms
{"5 ", 12}}}; // 150ms
OptionsField options_channel{
{11 * 8, 6 * 8},
5,
{{"Ch.37 ", 37},
{{"Ch.0", 0},
{"Ch.1", 1},
{"Ch.2", 2},
{"Ch.3", 3},
{"Ch.4", 4},
{"Ch.5", 5},
{"Ch.6", 6},
{"Ch.7", 7},
{"Ch.8", 8},
{"Ch.9", 9},
{"Ch.10", 10},
{"Ch.11", 11},
{"Ch.12", 12},
{"Ch.13", 13},
{"Ch.14", 14},
{"Ch.15", 15},
{"Ch.16", 16},
{"Ch.17", 17},
{"Ch.18", 18},
{"Ch.19", 19},
{"Ch.20", 20},
{"Ch.21", 21},
{"Ch.22", 22},
{"Ch.23", 23},
{"Ch.24", 24},
{"Ch.25", 25},
{"Ch.26", 26},
{"Ch.27", 27},
{"Ch.28", 28},
{"Ch.29", 29},
{"Ch.30", 30},
{"Ch.31", 31},
{"Ch.32", 32},
{"Ch.33", 33},
{"Ch.34", 34},
{"Ch.35", 35},
{"Ch.36", 36},
{"Ch.37", 37},
{"Ch.38", 38},
{"Ch.39", 39},
{"Auto", 40}}};
{"Auto", BLT_CHAN_AUTO},
{"All", BLT_CHAN_ALL}}};
OptionsField options_adv_type{
{17 * 8, 6 * 8},
@@ -254,7 +301,7 @@ class BLETxView : public View {
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
Labels label_marked_data{
{{0 * 8, 4 * 16}, "Marked Data:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 4 * 16}, "Marked Data:", Theme::getInstance()->fg_light->foreground}};
OptionsField marked_data_sequence{
{12 * 8, 8 * 8},
@@ -264,45 +311,42 @@ class BLETxView : public View {
{"Random", 2}}};
Labels label_packet_index{
{{0 * 8, 12 * 8}, "Packet Index:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 12 * 8}, "Packet Index:", Theme::getInstance()->fg_light->foreground}};
Text text_packet_index{
{13 * 8, 6 * 16, 12 * 8, 16},
"-"};
Labels label_packets_sent{
{{0 * 8, 14 * 8}, "Repeat Count:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 14 * 8}, "Repeat Count:", Theme::getInstance()->fg_light->foreground}};
Text text_packets_sent{
{13 * 8, 7 * 16, 12 * 8, 16},
"-"};
Labels label_mac_address{
{{0 * 8, 16 * 8}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 16 * 8}, "Mac Address:", Theme::getInstance()->fg_light->foreground}};
Text text_mac_address{
{12 * 8, 8 * 16, 20 * 8, 16},
"-"};
Labels label_data_packet{
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
Console console{
{0, 9 * 18, screen_width, screen_height - 80}};
{{UI_POS_X(0), 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
TextViewer dataEditView{
{0, 9 * 18, screen_width, screen_height - 80}};
{0, 9 * 18, 240, 240}};
Button button_clear_marked{
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
{UI_POS_X(1), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(13), UI_POS_HEIGHT(1.5)},
"Clear Marked"};
Button button_save_packet{
{1 * 8, 16 * 16, 13 * 8, 2 * 16},
{UI_POS_X(1), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
"Save Packet"};
Button button_switch{
{16 * 8, 16 * 16, 13 * 8, 2 * 16},
{UI_POS_X_RIGHT(14), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
"Switch to Rx"};
std::string str_log{""};
@@ -323,7 +367,7 @@ class BLETxView : public View {
Message::ID::TXProgress,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
this->on_tx_progress(message.done, message.progress);
}};
MessageHandlerRegistration message_handler_frame_sync{
+9 -9
View File
@@ -65,31 +65,31 @@ class CaptureAppView : public View {
}};
Labels labels{
{{0 * 8, 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 1 * 16}, "Rate:", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 1 * 16}, "Format:", Theme::getInstance()->fg_light->foreground},
};
RSSI rssi{
{24 * 8, 0, 6 * 8, 4}};
{UI_POS_X(24), 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{24 * 8, 5, 6 * 8, 4}};
{UI_POS_X(24), 5, UI_POS_WIDTH_REMAINING(24), 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
FrequencyStepView field_frequency_step{
{10 * 8, 0 * 16}};
{10 * 8, UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{16 * 8, 0 * 16}};
{16 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{21 * 8, 0 * 16}};
{21 * 8, UI_POS_Y(0)}};
OptionsField option_bandwidth{
{5 * 8, 1 * 16},
@@ -109,7 +109,7 @@ class CaptureAppView : public View {
/*small*/ true};
RecordView record_view{
{0 * 8, 2 * 16, screen_width, 1 * 16},
{UI_POS_X(0), 2 * 16, screen_width, 1 * 16},
u"BBD_????.*",
captures_dir,
RecordView::FileType::RawS16,
+131 -18
View File
@@ -28,6 +28,7 @@
#include "string_format.hpp"
#include "utility.hpp"
#include "file_path.hpp"
#include "usb_serial_asyncmsg.hpp"
using namespace portapack;
using namespace pocsag;
@@ -56,20 +57,24 @@ POCSAGSettingsView::POCSAGSettingsView(
: settings_{settings} {
add_children(
{&labels,
&opt_baud_rate,
&check_log,
&check_log_raw,
&check_small_font,
&check_hide_bad,
&check_hide_addr_only,
&check_numeric_detect,
&opt_filter_mode,
&field_filter_address,
&button_save});
opt_baud_rate.set_by_value(settings_.baud_rate);
check_log.set_value(settings_.enable_logging);
check_log_raw.set_value(settings_.enable_raw_log);
check_small_font.set_value(settings_.enable_small_font);
check_hide_bad.set_value(settings_.hide_bad_data);
check_hide_addr_only.set_value(settings_.hide_addr_only);
check_numeric_detect.set_value(settings_.enable_numeric_detect);
opt_filter_mode.set_by_value(settings_.filter_mode);
field_filter_address.set_value(settings_.filter_address);
@@ -79,9 +84,10 @@ POCSAGSettingsView::POCSAGSettingsView(
settings_.enable_small_font = check_small_font.value();
settings_.hide_bad_data = check_hide_bad.value();
settings_.hide_addr_only = check_hide_addr_only.value();
settings_.enable_numeric_detect = check_numeric_detect.value();
settings_.filter_mode = opt_filter_mode.selected_index_value();
settings_.filter_address = field_filter_address.to_integer();
settings_.baud_rate = opt_baud_rate.selected_index_value();
nav.pop();
};
}
@@ -142,7 +148,7 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
audio::output::start();
receiver_model.enable();
baseband::set_pocsag();
baseband::set_pocsag((int8_t)settings_.baud_rate);
}
void POCSAGAppView::focus() {
@@ -182,6 +188,7 @@ void POCSAGAppView::refresh_ui() {
btn_text = "Filter Last";
break;
}
baseband::set_pocsag((int8_t)settings_.baud_rate);
button_filter_last.set_text(btn_text);
}
@@ -216,12 +223,24 @@ void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefi
return;
}
// Color indicates the message has a lot of decoding errors.
std::string color = bad_data ? STR_COLOR_MAGENTA : STR_COLOR_WHITE;
// Type indicator with its own color.
std::string type_str;
bool numeric_detect = settings_.enable_numeric_detect;
if (numeric_detect && pocsag_state.detected == pocsag::DET_NUMERIC)
type_str = STR_COLOR_GREEN "n";
else if (numeric_detect && pocsag_state.detected == pocsag::DET_ALPHA)
type_str = STR_COLOR_LIGHT_GREY "a";
else if (pocsag_state.out_type == ADDRESS)
type_str = STR_COLOR_DARK_YELLOW "t";
else
type_str = "";
std::string console_info = "\n" + color + prefix;
console_info += " #" + to_string_dec_uint(pocsag_state.address);
// Header: timestamp+baud in light grey, capcode+function in white.
std::string console_info = "\n" STR_COLOR_LIGHT_GREY + prefix;
console_info += STR_COLOR_WHITE " #" + to_string_dec_uint(pocsag_state.address);
console_info += " F" + to_string_dec_uint(pocsag_state.function);
if (!type_str.empty())
console_info += " " + type_str;
if (pocsag_state.out_type == ADDRESS) {
last_address = pocsag_state.address;
@@ -237,23 +256,111 @@ void POCSAGAppView::handle_decoded(Timestamp timestamp, const std::string& prefi
}
}
/* Serial: tone-only page */
if (portapack::usb_serial.serial_connected()) {
std::string s = "\r\nPOCSAG " + to_string_dec_uint(current_bitrate) + " " + to_string_dec_uint(pocsag_state.address) + " " + std::string(1, 'A' + pocsag_state.function) + (current_inverted ? " I" : " S") + " tone";
UsbSerialAsyncmsg::asyncmsg(s);
}
} else if (pocsag_state.out_type == MESSAGE) {
if (pocsag_state.address != last_address) {
// New message
if (pocsag_state.new_message) {
last_address = pocsag_state.address;
serial_numeric_sent = 0;
console.writeln(console_info);
console.write(color + pocsag_state.output);
// If heuristic chose numeric, show numeric line first (green).
if (numeric_detect &&
pocsag_state.detected == pocsag::DET_NUMERIC &&
pocsag_state.numeric_len > 0) {
std::string num_str(pocsag_state.numeric_buf, pocsag_state.numeric_len);
console.write(STR_COLOR_GREEN + num_str);
console.writeln("");
}
// Alpha decode (already has per-char color escapes from decoder).
console.write(pocsag_state.output);
/* Serial: header + first chunk.
* hex field contains rendered alpha as hex bytes (color escapes
* stripped). Non-printable chars show as '.' (0x2E),
* uncorrectable chars show as '?' (0x3F) original 7-bit
* values are not preserved.
* Numeric decode goes in the quoted message field only. */
if (portapack::usb_serial.serial_connected()) {
/* Build hex representation of decoded alpha characters. */
std::string raw;
for (size_t i = 0; i < pocsag_state.output.size(); ++i)
raw += to_string_hex((uint8_t)pocsag_state.output[i], 2);
std::string s = "\r\nPOCSAG " + to_string_dec_uint(current_bitrate) + " " + to_string_dec_uint(pocsag_state.address) + " " + std::string(1, 'A' + pocsag_state.function) + (current_inverted ? " I" : " S");
if (numeric_detect && pocsag_state.detected == pocsag::DET_NUMERIC) {
s += " numeric \"";
if (pocsag_state.numeric_len > 0)
s += std::string(pocsag_state.numeric_buf, pocsag_state.numeric_len);
s += "\"";
serial_numeric_sent = pocsag_state.numeric_len;
} else {
/* For alpha, the quoted text is the same chars as raw but as ASCII.
* Escape " and \ to avoid breaking the quoted field. */
s += " alpha \"";
for (size_t i = 0; i < pocsag_state.output.size(); ++i) {
if (pocsag_state.output[i] == '"' || pocsag_state.output[i] == '\\') {
s += '\\';
}
s += pocsag_state.output[i];
}
s += "\"";
}
s += " hex:" + raw;
UsbSerialAsyncmsg::asyncmsg(s);
}
} else {
// Message continues...
console.write(color + pocsag_state.output);
// Message continues from previous batch.
bool is_numeric = numeric_detect && pocsag_state.detected == pocsag::DET_NUMERIC;
// GUI: show numeric continuation if applicable.
if (is_numeric && pocsag_state.numeric_len > serial_numeric_sent) {
std::string num_str(pocsag_state.numeric_buf + serial_numeric_sent,
pocsag_state.numeric_len - serial_numeric_sent);
console.write(STR_COLOR_GREEN + num_str);
}
console.write(pocsag_state.output);
/* Serial: continuation chunk with full header.
* Type label carries over from first batch (numeric+ or alpha+). */
if (portapack::usb_serial.serial_connected()) {
std::string raw;
std::string decoded;
for (size_t i = 0; i < pocsag_state.output.size(); ++i) {
if (pocsag_state.output[i] == '"' || pocsag_state.output[i] == '\\')
decoded += '\\';
decoded += pocsag_state.output[i];
raw += to_string_hex((uint8_t)pocsag_state.output[i], 2);
}
if (!decoded.empty() || pocsag_state.numeric_len > serial_numeric_sent) {
std::string s = "\r\nPOCSAG " + to_string_dec_uint(current_bitrate) + " " + to_string_dec_uint(pocsag_state.address) + " " + std::string(1, 'A' + pocsag_state.function) + (current_inverted ? " I" : " S") + (is_numeric ? " numeric+" : " alpha+") + " \"";
if (is_numeric && pocsag_state.numeric_len > serial_numeric_sent)
s += std::string(pocsag_state.numeric_buf + serial_numeric_sent,
pocsag_state.numeric_len - serial_numeric_sent);
else
s += decoded;
s += "\" hex:" + raw;
UsbSerialAsyncmsg::asyncmsg(s);
}
}
serial_numeric_sent = pocsag_state.numeric_len;
}
if (logging()) {
logger.log_decoded(
timestamp,
to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function) +
" " + pocsag_state.output);
std::string log_entry = to_string_dec_uint(pocsag_state.address) +
" F" + to_string_dec_uint(pocsag_state.function);
if (numeric_detect &&
pocsag_state.detected == pocsag::DET_NUMERIC &&
pocsag_state.numeric_len > 0)
log_entry += " N:" + std::string(pocsag_state.numeric_buf, pocsag_state.numeric_len);
log_entry += " " + pocsag_state.output;
logger.log_decoded(timestamp, log_entry);
}
}
}
@@ -297,13 +404,19 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
image_status.set_foreground(Theme::getInstance()->fg_magenta->foreground);
pocsag_state.codeword_index = 0;
pocsag_state.errors = 0;
current_bitrate = message->packet.bitrate();
current_inverted = message->packet.inverted();
// Handle multiple messages (if any).
while (pocsag_decode_batch(message->packet, pocsag_state))
handle_decoded(message->packet.timestamp(), prefix);
// Handle the remainder.
handle_decoded(message->packet.timestamp(), prefix);
// Handle the remainder. Skip if decoder is still in
// STATE_HAVE_ADDRESS — the address was at the end of this
// batch and we can't yet tell if it's tone-only or has
// message data in the next batch.
if (pocsag_state.mode != STATE_HAVE_ADDRESS)
handle_decoded(message->packet.timestamp(), prefix);
}
// Set status icon color to indicate state machine state.
+35 -13
View File
@@ -125,7 +125,9 @@ struct POCSAGSettings {
bool enable_raw_log = false;
bool hide_bad_data = false;
bool hide_addr_only = false;
bool enable_numeric_detect = true; /* heuristic alpha/numeric type detection */
uint8_t filter_mode = false;
int32_t baud_rate = -1;
uint32_t filter_address = 0;
};
@@ -139,9 +141,18 @@ class POCSAGSettingsView : public View {
private:
POCSAGSettings& settings_;
OptionsField opt_baud_rate{
{8 * 8, 0 * 16},
4,
{{"Auto", -1},
{" 512", 0},
{"1200", 1},
{"2400", 2}}};
Labels labels{
{{2 * 8, 12 * 16}, "Filter Mode:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 13 * 16}, "Filter Addr:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 0 * 16}, "Baud:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 14 * 16}, "Filter Mode:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 15 * 16}, "Filter Addr:", Theme::getInstance()->fg_light->foreground},
};
Checkbox check_log{
@@ -169,21 +180,26 @@ class POCSAGSettingsView : public View {
22,
"Hide Addr Only"};
Checkbox check_numeric_detect{
{2 * 8, 12 * 16},
22,
"Detect Numeric"};
OptionsField opt_filter_mode{
{15 * 8, 12 * 16},
{15 * 8, 14 * 16},
4,
{{"None", FILTER_NONE},
{"Drop", FILTER_DROP},
{"Keep", FILTER_KEEP}}};
SymField field_filter_address{
{15 * 8, 13 * 16},
{15 * 8, 15 * 16},
7,
SymField::Type::Dec,
true /*explicit_edit*/};
Button button_save{
{11 * 8, 16 * 16, 10 * 8, 2 * 16},
{UI_POS_X_CENTER(10), UI_POS_Y(17), 10 * 8, 2 * 16},
"Save"};
};
@@ -221,6 +237,8 @@ class POCSAGAppView : public View {
{"filter_address"sv, &settings_.filter_address},
{"hide_bad_data"sv, &settings_.hide_bad_data},
{"hide_addr_only"sv, &settings_.hide_addr_only},
{"numeric_detect"sv, &settings_.enable_numeric_detect},
{"baud_rate"sv, &settings_.baud_rate},
}};
void refresh_ui();
@@ -230,36 +248,40 @@ class POCSAGAppView : public View {
void on_stats(const POCSAGStatsMessage* stats);
uint32_t last_address = 0;
uint16_t current_bitrate = 0; /* bitrate of current packet being decoded */
bool current_inverted = false; /* polarity of current packet being decoded */
uint8_t serial_numeric_sent = 0; /* numeric chars already sent to serial/GUI */
pocsag::EccContainer ecc{};
pocsag::POCSAGState pocsag_state{&ecc};
POCSAGLogger logger{};
uint16_t packet_count = 0;
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
{UI_POS_X(0), 0 * 8},
nav_};
RFAmpField field_rf_amp{
{11 * 8, 0 * 16}};
{11 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{16 * 8, 0 * 16}};
{16 * 8, UI_POS_Y(0)}};
RSSI rssi{
{19 * 8 - 4, 3, 6 * 8, 4}};
{19 * 8 - 4, 3, UI_POS_WIDTH_REMAINING(26), 4}};
Audio audio{
{19 * 8 - 4, 8, 6 * 8, 4}};
{19 * 8 - 4, 8, UI_POS_WIDTH_REMAINING(26), 4}};
NumberField field_squelch{
{25 * 8, 0 * 16},
{UI_POS_X_RIGHT(6), UI_POS_Y(0)},
2,
{0, 99},
1,
' ',
true /*wrap*/};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Image image_status{
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
@@ -10,7 +10,7 @@ namespace ui {
constexpr std::string_view mayhem_information_list[] = {
"#****** Mayhem Community ******",
" ",
" https://discord.mayhem.app",
" https://discord.hackrf.app",
" ",
"#**** List of contributors ****",
" ",
@@ -34,6 +34,7 @@ constexpr std::string_view mayhem_information_list[] = {
"RedFox-Fr,nemanjan00,",
"MichalLeonBorsuk,",
"MatiasFernandez,Giorgiofox",
"TommasoVentafridda",
" ",
"#Havoc:",
"jboone,furrtek,eried,argilo,",
+124 -19
View File
@@ -51,7 +51,8 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
Color target_color;
std::string entry_string;
@@ -70,9 +71,11 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
target_color = Theme::getInstance()->fg_medium->foreground;
};
entry_string +=
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
std::string ipc = (entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ");
uint8_t firstcolwidth = columns.at(0).second;
ipc.resize(firstcolwidth, ' '); // Make sure this is always match the first column's width that is dynamic.
entry_string += ipc + to_string_dec_int((int32_t)(entry.pos.altitude / 100), 4);
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
// It is generally accepted that for every thousand feet of altitude,
@@ -100,7 +103,7 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
entry_string);
if (entry.pos.pos_valid)
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
painter.draw_bitmap(target_rect.location() + Point(firstcolwidth * 8 - 8, 0),
bitmap_target, target_color, style.background);
}
@@ -268,10 +271,17 @@ ADSBRxDetailsView::ADSBRxDetailsView(
&text_frame_pos_even,
&text_frame_pos_odd,
&button_aircraft_details,
&button_see_map});
&button_see_map,
&opt_map_list});
text_icao_address.set(entry_.icao_str);
opt_map_list.on_change = [this](size_t, uint32_t mf) {
map_filter = mf;
clear_map_markers(); // reset them
pos_history.clear(); // erase the trail
};
button_aircraft_details.on_select = [this, &nav](Button&) {
aircraft_details_view_ = nav.push<ADSBRxAircraftDetailsView>(entry_);
nav.set_on_pop([this]() {
@@ -285,7 +295,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
get_map_tag(entry_),
entry_.pos.altitude,
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::HIDDEN,
GeoPos::spd_unit::KNOTS,
entry_.pos.latitude,
entry_.pos.longitude,
entry_.velo.heading);
@@ -309,17 +319,107 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
// AC Details view is showing, nothing to update.
} else if (geomap_view_) {
// Map is showing, update the current item.
if (map_filter == 1) {
// add to map trail history
add_map_trail(entry);
}
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);
geomap_view_->update_position(entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude, entry.get_ground_speed());
} else {
// Details is showing, update details.
refresh_ui();
}
}
void ADSBRxDetailsView::get_altitude_color(int32_t alt_ft, uint8_t* r, uint8_t* g, uint8_t* b) {
static const struct {
int32_t alt;
uint8_t r, g, b;
} stops[] = {
{0, 220, 220, 220}, // 0 ft: White/Grey (Ground)
{2000, 255, 255, 0}, // 2k ft: Yellow
{10000, 0, 255, 0}, // 10k ft: Green
{20000, 0, 255, 255}, // 20k ft: Cyan
{30000, 60, 60, 255}, // 30k ft: Blue (Lightened for visibility on Black)
{40000, 255, 0, 255} // 40k+ ft: Magenta
};
if (alt_ft <= stops[0].alt) {
*r = stops[0].r;
*g = stops[0].g;
*b = stops[0].b;
return;
}
const int count = sizeof(stops) / sizeof(stops[0]);
if (alt_ft >= stops[count - 1].alt) {
*r = stops[count - 1].r;
*g = stops[count - 1].g;
*b = stops[count - 1].b;
return;
}
for (int i = 0; i < count - 1; i++) {
if (alt_ft < stops[i + 1].alt) {
float t = (float)(alt_ft - stops[i].alt) / (stops[i + 1].alt - stops[i].alt);
*r = (uint8_t)(stops[i].r + (stops[i + 1].r - stops[i].r) * t);
*g = (uint8_t)(stops[i].g + (stops[i + 1].g - stops[i].g) * t);
*b = (uint8_t)(stops[i].b + (stops[i + 1].b - stops[i].b) * t);
return;
}
}
// Fallback: in case no interval matched, default to ground color.
*r = stops[0].r;
*g = stops[0].g;
*b = stops[0].b;
}
void ADSBRxDetailsView::clear_map_markers() {
if (geomap_view_)
geomap_view_->clear_markers();
if (map_filter == 1)
refresh_trail_markers(); // re add trail
}
void ADSBRxDetailsView::refresh_trail_markers() {
if (!geomap_view_)
return;
geomap_view_->clear_markers();
for (const auto& pos : pos_history) {
GeoMarker marker{};
marker.lon = pos.lon;
marker.lat = pos.lat;
marker.angle = pos.heading;
marker.tag = ""; // No tag for trail points
uint8_t r, g, b;
get_altitude_color(pos.altitude, &r, &g, &b);
marker.color = Color(r, g, b);
geomap_view_->store_marker(marker);
}
}
void ADSBRxDetailsView::add_map_trail(const AircraftRecentEntry& entry) {
if (!geomap_view_)
return;
if (entry.pos.pos_valid == false)
return;
if (!pos_history.empty()) {
const auto& last_pos = pos_history.back();
const float THRESH_DEG = 0.009f;
const float THRESH_SQ = THRESH_DEG * THRESH_DEG; // 0.00002025f
float d_lat = entry.pos.latitude - last_pos.lat;
float d_lon = entry.pos.longitude - last_pos.lon;
if ((d_lat * d_lat) + (d_lon * d_lon) < THRESH_SQ) {
return; // Moved less than ~1000m, skip (rough estimate, varies with latitude but good enough for our purposes). This prevents adding too many points when the plane is circling or taxiing.
}
}
// Only keep the last 30 positions in the trail.
if (pos_history.size() >= 30)
pos_history.erase(pos_history.begin());
pos_history.push_back({entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude});
refresh_trail_markers();
}
bool ADSBRxDetailsView::add_map_marker(const AircraftRecentEntry& entry) {
@@ -327,11 +427,16 @@ bool ADSBRxDetailsView::add_map_marker(const AircraftRecentEntry& entry) {
if (!geomap_view_)
return false;
if (map_filter == 1) return false; // this is the 'only me' option, so skip all others
GeoMarker marker{};
marker.lon = entry.pos.longitude;
marker.lat = entry.pos.latitude;
marker.angle = entry.velo.heading;
marker.tag = get_map_tag(entry);
uint8_t r, g, b;
get_altitude_color(entry.pos.altitude, &r, &g, &b);
marker.color = Color(r, g, b);
auto markerStored = geomap_view_->store_marker(marker);
return markerStored == MARKER_STORED;
@@ -409,7 +514,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
&status_good_frame,
&field_volume});
recent_entries_view.set_parent_rect({0, 16, screen_width, 272});
recent_entries_view.set_parent_rect({0, 16, screen_width, UI_POS_HEIGHT_REMAINING(2)});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detail_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(entry);
@@ -563,9 +668,8 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
(raw_data[3] & 15);
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
// altitude = 25N - 1000 (ft); minimum is -1000 ft when N=0 (Q=1, M=0 per ICAO Annex 10).
altitude = 25 * n - 1000;
if (altitude < 0)
altitude = 0;
} // else N is an 11 bit Gillham coded altitude
}
@@ -635,27 +739,28 @@ void ADSBRxView::refresh_ui() {
if (details_view->map_active()) {
// Is it time to clear and refresh the map's markers?
if (ticks_since_marker_refresh >= MARKER_UPDATE_SECONDS) {
if (ticks_since_marker_refresh >= (details_view->get_map_type() == MAP_TYPE_OSM ? MARKER_UPDATE_SECONDS_OSM : MARKER_UPDATE_SECONDS_BIN)) {
map_needs_update = true;
ticks_since_marker_refresh = 0;
details_view->clear_map_markers();
}
} else {
// Refresh map immediately once active.
ticks_since_marker_refresh = MARKER_UPDATE_SECONDS;
ticks_since_marker_refresh = MARKER_UPDATE_SECONDS_OSM; // this is the bigger, so set it to force
}
// Process the entries list.
for (const auto& entry : recent) {
// Found the entry being shown in details view. Update it.
if (entry.key() == detail_key) {
if (entry.key() == detail_key) { // we don't add it to the markers, since this is the currently selected and shown in the middle one. This eliminates the "shadow" marker, and saves ram
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.pos_valid && entry.state <= ADSBAgeState::Recent) {
map_needs_update = details_view->add_map_marker(entry);
} else {
// Add others only
// NB: current entry also gets a marker so it shows up if map is panned.
if (map_needs_update && entry.pos.pos_valid && entry.state <= ADSBAgeState::Recent) {
map_needs_update = details_view->add_map_marker(entry);
}
}
// Any work left to do?
+89 -50
View File
@@ -64,8 +64,9 @@ namespace ui {
#define VEL_AIR_SUBSONIC 3
#define VEL_AIR_SUPERSONIC 4
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define MARKER_UPDATE_SECONDS 5 // "other" map marker redraw interval
#define O_E_FRAME_TIMEOUT 20 // timeout between odd and even frames
#define MARKER_UPDATE_SECONDS_OSM 8 // "other" map marker redraw interval for osm
#define MARKER_UPDATE_SECONDS_BIN 5 // "other" map marker redraw interval for bin map
/* Thresholds (in seconds) that define the transition between ages. */
struct ADSBAgeLimit {
@@ -149,6 +150,19 @@ struct AircraftRecentEntry {
age = 0;
}
int32_t get_ground_speed() const {
if (velo.valid == false) return 0;
if (velo.type == SPD_GND)
return velo.speed;
else if (velo.type == SPD_IAS) {
if (!pos.alt_valid) return velo.speed; // can't correct without altitude, so just return IAS
return (int32_t)(velo.speed * (1.0f + (0.02f * (pos.altitude / 1000.0f))));
} else if (velo.type == SPD_TAS)
return velo.speed; // We don't know the wind speed
else
return 0;
}
void inc_age(int delta) {
age += delta;
@@ -207,54 +221,54 @@ class ADSBRxAircraftDetailsView : public View {
private:
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Registration:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Manufacturer:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Model:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Number of engines:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 9 * 16}, "Engine type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 11 * 16}, "Owner:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, "Operator:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(1)}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(2)}, "Registration:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(3)}, "Manufacturer:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(5)}, "Model:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(7)}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(8)}, "Number of engines:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(9)}, "Engine type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(11)}, "Owner:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(13)}, "Operator:", Theme::getInstance()->fg_light->foreground}};
Text text_icao_address{
{5 * 8, 1 * 16, 6 * 8, 16},
{UI_POS_X(5), UI_POS_Y(1), 6 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_registration{
{13 * 8, 2 * 16, 8 * 8, 16},
{UI_POS_X(13), UI_POS_Y(2), 8 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_manufacturer{
{0 * 8, 4 * 16, 19 * 8, 16},
{UI_POS_X(0), UI_POS_Y(4), 19 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_model{
{0 * 8, 6 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(6), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_type{
{5 * 8, 7 * 16, 22 * 8, 16},
{UI_POS_X(5), UI_POS_Y(7), 22 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_number_of_engines{
{18 * 8, 8 * 16, screen_width, 16},
{UI_POS_X(18), UI_POS_Y(8), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_engine_type{
{0 * 8, 10 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(10), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_owner{
{0 * 8, 12 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(12), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_operator{
{0 * 8, 14 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(14), screen_width, UI_POS_HEIGHT(1)},
"-"};
Button button_close{
{9 * 8, 16 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12), UI_POS_Y(16), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"Back"};
};
@@ -269,14 +283,24 @@ class ADSBRxDetailsView : public View {
void focus() override;
void update(const AircraftRecentEntry& entry);
void get_altitude_color(int32_t alt_ft, uint8_t* r, uint8_t* g, uint8_t* b);
/* 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);
void add_map_trail(const AircraftRecentEntry& entry);
void refresh_trail_markers();
std::string title() const override { return "Details"; }
MapType get_map_type() {
if (geomap_view_)
return geomap_view_->get_map_type();
else
return MapType::MAP_TYPE_BIN; // default
}
private:
void refresh_ui();
void on_gps(const GPSPosDataMessage* msg);
@@ -289,57 +313,72 @@ class ADSBRxDetailsView : public View {
// if removed from the recent entries list.
AircraftRecentEntry entry_{AircraftRecentEntry::invalid_key};
bool airline_checked{false};
uint8_t map_filter{0}; // 0: all, 1: only this, set by opt_map_list.
struct PosHistory {
float lat;
float lon;
uint16_t heading;
int32_t altitude;
};
std::vector<PosHistory> pos_history{};
Labels labels{
{{0 * 8, 1 * 16}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{13 * 8, 1 * 16}, "Callsign:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Last seen:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Airline:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Country:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, "Even position frame:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 15 * 16}, "Odd position frame:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(0)}, "ICAO:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(13), UI_POS_Y(0)}, "Callsign:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(1)}, "Last seen:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(2)}, "Airline:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(4)}, "Country:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(7)}, "Map filter:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(13)}, "Even position frame:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(15)}, "Odd position frame:", Theme::getInstance()->fg_light->foreground}};
Text text_icao_address{
{5 * 8, 1 * 16, 6 * 8, 16},
{UI_POS_X(5), UI_POS_Y(0), 6 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_callsign{
{22 * 8, 1 * 16, 8 * 8, 16},
{UI_POS_X(22), UI_POS_Y(0), 8 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_last_seen{
{11 * 8, 2 * 16, 19 * 8, 16},
{UI_POS_X(11), UI_POS_Y(1), 19 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_airline{
{0 * 8, 4 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(3), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_country{
{8 * 8, 5 * 16, 22 * 8, 16},
{UI_POS_X(8), UI_POS_Y(4), 22 * 8, UI_POS_HEIGHT(1)},
"-"};
Text text_infos{
{0 * 8, 6 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(5), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_info2{
{0 * 8, 7 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(6), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_frame_pos_even{
{0 * 8, 14 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(14), screen_width, UI_POS_HEIGHT(1)},
"-"};
Text text_frame_pos_odd{
{0 * 8, 16 * 16, screen_width, 16},
{UI_POS_X(0), UI_POS_Y(16), screen_width, UI_POS_HEIGHT(1)},
"-"};
Button button_aircraft_details{
{2 * 8, 9 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) - UI_POS_X(8), UI_POS_Y(8), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"A/C details"};
OptionsField opt_map_list{
{UI_POS_X_CENTER(12) + UI_POS_X(8), UI_POS_Y(7)},
12,
{{"All", 0},
{"Only this", 1}}};
Button button_see_map{
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) + UI_POS_X(8), UI_POS_Y(8), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See on map"};
MessageHandlerRegistration message_handler_gps{
@@ -388,15 +427,15 @@ class ADSBRxView : public View {
SignalToken signal_token_tick_second{};
uint8_t tick_count = 0;
uint16_t ticks_since_marker_refresh{MARKER_UPDATE_SECONDS};
uint16_t ticks_since_marker_refresh{MARKER_UPDATE_SECONDS_OSM};
/* Max number of entries that can be updated in a single pass.
* 16 is one screen of recent entries. */
static constexpr uint8_t max_update_entries = 16;
/* Recent Entries */
const RecentEntriesColumns columns{
{{"ICAO/Call", 9},
RecentEntriesColumns columns{
{{"ICAO/Call", 0},
{"Lvl", 3},
{"Spd", 3},
{"Amp", 3},
@@ -416,33 +455,33 @@ class ADSBRxView : public View {
ADSBRxDetailsView* details_view{nullptr};
Labels labels{
{{0 * 8, 0 * 8}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP:", Theme::getInstance()->fg_light->foreground}};
LNAGainField field_lna{
{4 * 8, 0 * 16}};
{UI_POS_X(4), UI_POS_Y(0)}};
VGAGainField field_vga{
{11 * 8, 0 * 16}};
{UI_POS_X(11), UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{20 * 8, 4, 7 * 8, 8},
{UI_POS_X(20), 4, UI_POS_WIDTH_REMAINING(23), 8},
};
ActivityDot status_frame{
{27 * 8 + 2, 5, 2, 2},
{UI_POS_X_RIGHT(3) + 2, 5, 2, 2},
Theme::getInstance()->bg_darkest->foreground,
};
ActivityDot status_good_frame{
{27 * 8 + 2, 9, 2, 2},
{UI_POS_X_RIGHT(3) + 2, 9, 2, 2},
Theme::getInstance()->fg_green->foreground,
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
MessageHandlerRegistration message_handler_frame{
Message::ID::ADSBFrame,
+6 -5
View File
@@ -42,7 +42,8 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
Color target_color;
// auto entry_age = entry.age;
@@ -51,8 +52,8 @@ void RecentEntriesTable<APRSRecentEntries>::draw(
std::string entry_string = "";
entry_string += entry.source_formatted;
entry_string.append(10 - entry.source_formatted.size(), ' ');
entry_string += " ";
entry_string.resize(columns.at(0).second, ' ');
entry_string += " ";
entry_string += (entry.hits <= 999 ? to_string_dec_uint(entry.hits, 4) : "999+");
entry_string += " ";
entry_string += entry.time_string;
@@ -247,8 +248,8 @@ APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec)
details_view.hidden(true);
recent_entries_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
details_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
recent_entries_view.set_parent_rect({0, 0, screen_width, screen_height - 40});
details_view.set_parent_rect({0, 0, screen_width, screen_height - 40});
recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
this->on_show_detail(entry);
+18 -18
View File
@@ -134,14 +134,14 @@ class APRSDetailsView : public View {
bool send_updates{false};
Console console{
{0, 0 * 16, screen_width, 224}};
{UI_POS_X(0), UI_POS_Y(0), screen_width, screen_height - 80}};
Button button_done{
{160, 14 * 16, 8 * 8, 3 * 16},
{UI_POS_X_CENTER(4) - UI_POS_WIDTH(8), UI_POS_Y(14), UI_POS_WIDTH(8), UI_POS_HEIGHT(3)},
"Close"};
Button button_see_map{
{80, 14 * 16, 8 * 8, 3 * 16},
{UI_POS_X_CENTER(4) + UI_POS_WIDTH(8), UI_POS_Y(14), UI_POS_WIDTH(8), UI_POS_HEIGHT(3)},
"Map"};
};
@@ -161,10 +161,10 @@ class APRSTableView : public View {
private:
NavigationView& nav_;
const RecentEntriesColumns columns{{{"Source", 9},
{"Loc", 6},
{"Hits", 4},
{"Time", 8}}};
RecentEntriesColumns columns{{{"Source", 0},
{"Loc", 6},
{"Hits", 4},
{"Time", 8}}};
APRSRecentEntries recent{};
RecentEntriesView<RecentEntries<APRSRecentEntry>> recent_entries_view{columns, recent};
APRSDetailsView details_view{nav_};
@@ -209,21 +209,21 @@ class APRSRxView : public View {
std::string str_log{""};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21) - UI_POS_WIDTH(2), 4}};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
OptionsField options_region{
{0 * 8, 0 * 8},
{UI_POS_X(0), UI_POS_Y(0)},
3,
{{"MAN", 0},
{"NA ", 1},
@@ -237,11 +237,11 @@ class APRSRxView : public View {
{"ISS", 9}}};
FrequencyField field_frequency{
{3 * 8, 0 * 16}};
{UI_POS_X(3), UI_POS_Y(0)}};
// DEBUG
RecordView record_view{
{0 * 8, 1 * 16, screen_width, 1 * 16},
{UI_POS_X(0), UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
u"AFS_????.WAV",
aprs_dir,
RecordView::FileType::WAV,
@@ -249,7 +249,7 @@ class APRSRxView : public View {
4};
Console console{
{0, 2 * 16, screen_width, screen_height - 80}};
{UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, screen_height - 80}};
std::unique_ptr<APRSLogger> logger{};
};
@@ -265,7 +265,7 @@ class APRSRXView : public View {
private:
NavigationView& nav_;
Rect view_rect = {0, 3 * 8, screen_width, screen_height - 40};
Rect view_rect = {UI_POS_X(0), 3 * 8, UI_POS_MAXWIDTH, screen_height - 40};
APRSRxView view_stream{nav_, view_rect};
APRSTableView view_table{nav_, view_rect};
+125 -1
View File
@@ -29,6 +29,7 @@
#include "baseband_api.hpp"
#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_geomap.hpp"
#include <cstring>
#include <stdio.h>
@@ -49,10 +50,24 @@ APRSTXView::~APRSTXView() {
void APRSTXView::start_tx() {
// TODO: Clean up this API to take string_views to avoid allocations.
std::string new_payload = payload;
std::string gps = text_gps_coord.get();
std::string token = "?GPS?";
size_t pos = 0;
while ((pos = new_payload.find(token, pos)) != std::string::npos) {
new_payload.replace(pos, token.size(), gps);
pos += gps.size();
}
text_payload.set(new_payload);
std::string path = text_path.get();
make_aprs_frame(
sym_source.to_string().c_str(), num_ssid_source.value(),
sym_dest.to_string().c_str(), num_ssid_dest.value(),
payload);
new_payload, path);
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
// text_payload.set(to_string_hex_array(bb_data_ptr + 56, 15));
@@ -77,6 +92,61 @@ void APRSTXView::on_tx_progress(const uint32_t progress, const bool done) {
}
}
void APRSTXView::process_coordinates(float lat_, float lon_) {
std::string s;
char ns = lat_ >= 0 ? 'N' : 'S';
float lat = (lat_ >= 0) ? lat_ : -lat_;
uint32_t lat_d = (uint32_t)lat;
uint32_t lat_m = (uint32_t)((lat - lat_d) * 6000.0f + 0.5f);
if (lat_m >= 6000) {
lat_m = 0;
lat_d++;
}
s += to_string_dec_uint(lat_d, 2, '0');
s += to_string_dec_uint(lat_m / 100, 2, '0');
s += ".";
s += to_string_dec_uint(lat_m % 100, 2, '0');
s += ns;
s += "/";
char ew = lon_ >= 0 ? 'E' : 'W';
float lon = (lon_ >= 0) ? lon_ : -lon_;
uint32_t lon_d = (uint32_t)lon;
uint32_t lon_m = (uint32_t)((lon - lon_d) * 6000.0f + 0.5f);
if (lon_m >= 6000) {
lon_m = 0;
lon_d++;
}
s += to_string_dec_uint(lon_d, 3, '0');
s += to_string_dec_uint(lon_m / 100, 2, '0');
s += ".";
s += to_string_dec_uint(lon_m % 100, 2, '0');
s += ew;
text_gps_coord.set(s);
}
void APRSTXView::on_gps(const GPSPosDataMessage* message) {
if (manual_gps_mode) {
return; // no more update once set manually
}
if (message->lat < -90.0 || message->lat > 90.0 ||
message->lon < -180.0 || message->lon > 180.0) {
return;
}
if (message->lat == 0.0f && message->lon == 0.0f) {
return;
}
last_lat = message->lat;
last_lon = message->lon;
process_coordinates(message->lat, message->lon);
}
APRSTXView::APRSTXView(NavigationView& nav) {
baseband::run_image(portapack::spi_flash::image_tag_afsk);
@@ -87,8 +157,33 @@ APRSTXView::APRSTXView(NavigationView& nav) {
&num_ssid_dest,
&text_payload,
&button_set,
&text_gps_coord,
&button_mangps,
&gps_is_manual,
&text_path,
&button_setpath,
&tx_view});
sym_source.set_value(symsrc);
num_ssid_source.set_value(ssidsrc);
sym_dest.set_value(symdst);
num_ssid_dest.set_value(ssiddst);
text_payload.set(payload);
text_path.set(path_cache);
sym_source.on_change = [this](SymField&) {
symsrc = sym_source.to_string();
};
num_ssid_source.on_change = [this](int32_t v) {
ssidsrc = v;
};
sym_dest.on_change = [this](SymField&) {
symdst = sym_dest.to_string();
};
num_ssid_dest.on_change = [this](int32_t v) {
ssiddst = v;
};
button_set.on_select = [this, &nav](Button&) {
text_prompt(
nav,
@@ -99,6 +194,17 @@ APRSTXView::APRSTXView(NavigationView& nav) {
text_payload.set(s);
});
};
button_setpath.on_select = [this, &nav](Button&) {
text_prompt(
nav,
path_cache,
60,
ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& s) {
text_path.set(s);
path_cache = s;
});
};
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
@@ -116,6 +222,24 @@ APRSTXView::APRSTXView(NavigationView& nav) {
tx_view.set_transmitting(false);
transmitter_model.disable();
};
button_mangps.on_select = [this, &nav](Button&) {
nav.push<GeoMapView>(
0,
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN,
last_lat,
last_lon,
[this](int32_t, float lat, float lon, int32_t) {
last_lat = lat;
last_lon = lon;
manual_gps_mode = true;
gps_is_manual.set("MANUAL");
process_coordinates(lat, lon);
});
};
// process_coordinates(last_lat, last_lon); //don't load last, so won't confuse users
}
} /* namespace ui */
+63 -13
View File
@@ -50,59 +50,109 @@ class APRSTXView : public View {
1750000 /* bandwidth */,
AFSK_TX_SAMPLERATE /* sampling rate */
};
app_settings::SettingsManager settings_{
"tx_aprs", app_settings::Mode::TX};
std::string symsrc = "";
std::string symdst = "";
std::string payload{""};
std::string path_cache = "";
int32_t ssidsrc = 0;
int32_t ssiddst = 0;
bool manual_gps_mode = false;
float last_lat = 0.0f;
float last_lon = 0.0f;
app_settings::SettingsManager settings_{
"tx_aprs",
app_settings::Mode::TX,
{{"symsrc"sv, &symsrc},
{"symdst"sv, &symdst},
{"ssidsrc"sv, &ssidsrc},
{"ssiddst"sv, &ssiddst},
{"payload"sv, &payload},
{"last_lat"sv, &last_lat},
{"last_lon"sv, &last_lon},
{"path"sv, &path_cache}}};
void start_tx();
void generate_frame();
void generate_frame_pos();
void on_tx_progress(const uint32_t progress, const bool done);
void on_gps(const GPSPosDataMessage* message);
void process_coordinates(float lat, float lon);
Labels labels{
{{0 * 8, 1 * 16}, "Source: SSID:", Theme::getInstance()->fg_light->foreground}, // 6 alphanum + SSID
{{0 * 8, 2 * 16}, " Dest.: SSID:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Info field:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Source: SSID:", Theme::getInstance()->fg_light->foreground}, // 6 alphanum + SSID
{{UI_POS_X(0), UI_POS_Y(1)}, " Dest.: SSID:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(2)}, "Path: (ex.: WIDE1-1,WIDE2-1)", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(6)}, "Info field:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(10)}, "GPS:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(22), UI_POS_Y(14)}, "Use ?GPS? in the info", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(17), UI_POS_Y(15)}, "as a placeholder.", Theme::getInstance()->fg_light->foreground},
};
Text text_path{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
"WIDE1-1",
};
Text gps_is_manual{
{UI_POS_X(6), UI_POS_Y(10), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"",
};
SymField sym_source{
{7 * 8, 1 * 16},
{UI_POS_X(7), UI_POS_Y(0)},
6,
SymField::Type::Alpha};
NumberField num_ssid_source{
{19 * 8, 1 * 16},
{UI_POS_X(19), UI_POS_Y(0)},
2,
{0, 15},
1,
' '};
SymField sym_dest{
{7 * 8, 2 * 16},
{UI_POS_X(7), UI_POS_Y(1)},
6,
SymField::Type::Alpha};
NumberField num_ssid_dest{
{19 * 8, 2 * 16},
{UI_POS_X(19), UI_POS_Y(1)},
2,
{0, 15},
1,
' '};
Text text_payload{
{0 * 8, 5 * 16, screen_width, 16},
"-"};
{UI_POS_X(0), UI_POS_Y(7), UI_POS_MAXWIDTH, UI_POS_HEIGHT(1)},
""};
Button button_set{
{0 * 8, 6 * 16, 80, 32},
{UI_POS_X(0), UI_POS_Y(8), UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
"Set"};
Text text_gps_coord{
{UI_POS_X(0), UI_POS_Y(11), UI_POS_WIDTH(20), UI_POS_HEIGHT(1)},
"-"};
Button button_mangps{
{UI_POS_X(0), UI_POS_Y(12), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Manual GPS"};
Button button_setpath{
{UI_POS_X(0), UI_POS_Y(4), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Set Path"};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
5000,
0 // disable setting bandwith, since APRS used fixed 10k bandwidth
};
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
[this](Message* const p) {
const auto message = static_cast<const GPSPosDataMessage*>(p);
this->on_gps(message);
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
+16 -18
View File
@@ -27,6 +27,8 @@
#include "portapack.hpp"
#include "battery.hpp"
#include <cstring>
#include "ui_settings.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
@@ -50,7 +52,6 @@ void BattinfoView::update_result() {
text_voltage.set("UNKNOWN");
text_current.set("-");
text_charge.set("-");
// text_cycles.set("-");
text_ttef.set("-");
text_method.set("-");
// text_warn.set("");
@@ -58,7 +59,9 @@ void BattinfoView::update_result() {
}
bool uichg = false;
uint8_t valid_mask = 0;
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current);
bool battMayChanged = false;
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current, battMayChanged);
text_state.set((battMayChanged || !persistent_memory::battery_cap_valid()) ? "Needs config." : "ok");
// update text fields
if (percent <= 100 && (valid_mask & battery::BatteryManagement::BATT_VALID_VOLTAGE) == battery::BatteryManagement::BATT_VALID_VOLTAGE)
text_percent.set(to_string_dec_uint(percent) + " %");
@@ -74,6 +77,7 @@ void BattinfoView::update_result() {
labels_opt.hidden(false);
text_current.hidden(false);
text_charge.hidden(false);
text_state.hidden(false);
text_current.set(to_string_dec_int(current) + " mA");
if (current >= 25) // when >25mA it is charging in any scenario
text_charge.set("Charging");
@@ -96,7 +100,7 @@ void BattinfoView::update_result() {
labels_opt.hidden(true);
text_current.hidden(true);
text_charge.hidden(true);
// text_cycles.hidden(true);
text_state.hidden(true);
text_ttef.hidden(true);
// text_warn.set("");
}
@@ -138,14 +142,13 @@ void BattinfoView::update_result() {
}
if ((valid_mask & battery::BatteryManagement::BATT_VALID_PERCENT) == battery::BatteryManagement::BATT_VALID_PERCENT) {
text_method.set("IC");
button_mode.set_text("Volt");
} else {
text_method.set("Voltage");
button_mode.set_text("IC");
}
if (battMayChanged) text_capacity.set_style(Theme::getInstance()->fg_red);
if (uichg) set_dirty();
// to update status bar too, send message in behalf of batt manager
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage};
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage, battMayChanged};
EventDispatcher::send_message(msg);
}
@@ -158,26 +161,21 @@ BattinfoView::BattinfoView(NavigationView& nav)
&text_current,
&text_charge,
&text_method,
&button_mode,
&button_settings,
&button_exit,
// &text_cycles,
&text_capacity,
//&text_state, //disabled for now
// &text_warn,
&text_ttef});
button_exit.on_select = [this, &nav](Button&) {
nav.pop();
};
button_mode.on_select = [this, &nav](Button&) {
if (button_mode.text() == "IC") {
battery::BatteryManagement::set_calc_override(false);
persistent_memory::set_ui_override_batt_calc(false);
button_mode.set_text("Volt");
} else {
battery::BatteryManagement::set_calc_override(true);
persistent_memory::set_ui_override_batt_calc(true);
button_mode.set_text("IC");
}
button_settings.on_select = [this, &nav](Button&) {
nav.replace<SetBatteryView>();
};
text_capacity.set(to_string_dec_uint(persistent_memory::battery_cap_mah()) + " mAh");
if (!persistent_memory::battery_cap_valid()) text_capacity.set_style(Theme::getInstance()->fg_red);
update_result();
if (thread == nullptr) thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, BattinfoView::static_fn, this);
}
+30 -24
View File
@@ -54,51 +54,57 @@ class BattinfoView : public View {
int32_t current = 0;
Labels labels{
{{2 * 8, 1 * 16}, "Percent:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 2 * 16}, "Voltage:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 3 * 16}, "Method:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(1)}, "Percent:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(2)}, "Voltage:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(3)}, "Method:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(4)}, "Capacity:", Theme::getInstance()->fg_light->foreground},
};
Labels labels_opt{
{{2 * 8, 4 * 16}, "Current:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 5 * 16}, "Charge:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 6 * 16}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
// {{2 * 8, 7 * 16}, "Cycles:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "Change method:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(5)}, "Current:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(6)}, "Charge:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(7)}, "TTF/E:", Theme::getInstance()->fg_light->foreground},
//{{UI_POS_X(2), UI_POS_Y(8)}, "State:", Theme::getInstance()->fg_light->foreground}, //for now, this is disabled. see max ic implementation
{{UI_POS_X(2), UI_POS_Y(10)}, "Change settings:", Theme::getInstance()->fg_light->foreground},
};
Text text_percent{
{13 * 8, 1 * 16, 10 * 16, 16},
{UI_POS_X(13), UI_POS_Y(1), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
"-"};
Text text_voltage{
{13 * 8, 2 * 16, 10 * 16, 16},
{UI_POS_X(13), UI_POS_Y(2), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
"-"};
Text text_method{
{13 * 8, 3 * 16, 10 * 16, 16},
{UI_POS_X(13), UI_POS_Y(3), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
"-"};
Text text_capacity{
{UI_POS_X(13), UI_POS_Y(4), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
"-"};
Text text_current{
{13 * 8, 4 * 16, 10 * 16, 16},
{UI_POS_X(13), UI_POS_Y(5), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
"-"};
Text text_charge{
{13 * 8, 5 * 16, 10 * 16, 16},
{UI_POS_X(13), UI_POS_Y(6), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
"-"};
Text text_ttef{
{13 * 8, 6 * 16, 10 * 16, 16},
"-"};
/* Text text_cycles{
{13 * 8, 7 * 16, 10 * 16, 16},
{UI_POS_X(13), UI_POS_Y(7), UI_POS_WIDTH(11), UI_POS_HEIGHT(1)},
"-"};
Text text_state{
{UI_POS_X(13), UI_POS_Y(8), UI_POS_WIDTH_REMAINING(13), UI_POS_HEIGHT(1)},
"ok"};
/*
Text text_warn{
{1 * 8, 8 * 16, screen_width, 2 * 16},
""}; */
{UI_POS_X(1), UI_POS_Y(9), screen_width, UI_POS_HEIGHT(2)},
""};
*/
Button button_mode{
{2 * 8, 11 * 16 + 5, 5 * 16, 32},
"Volt"};
Button button_settings{
{UI_POS_X(2), UI_POS_Y(11) + 5, UI_POS_WIDTH(10), UI_POS_HEIGHT(2)},
"Settings"};
Button button_exit{
{72, 17 * 16, 96, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Back"};
static msg_t static_fn(void* arg);
Thread* thread{nullptr};
@@ -106,4 +112,4 @@ class BattinfoView : public View {
} /* namespace ui */
#endif /*__UI_BATTINFO__*/
#endif /*__UI_BATTINFO__*/
+7 -7
View File
@@ -63,22 +63,22 @@ class BTLERxView : public View {
static constexpr uint8_t channel_number = 37;
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(21), 4}};
Channel channel{
{21 * 8, 5, 6 * 8, 4}};
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(21), 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
Button button_modem_setup{
{screen_width - 12 * 8, 1 * 16, 96, 24},
{screen_width - 12 * 8, UI_POS_Y(1), 96, 24},
"Modem setup"};
Console console{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -12,7 +12,7 @@ BatteryCapacityView::RegisterEntry BatteryCapacityView::get_entry(size_t index)
BatteryCapacityView::BatteryCapacityView(NavigationView& nav) {
for (size_t i = 0; i < ENTRIES_PER_PAGE; ++i) {
name_texts[i].set_parent_rect({0 * 8, static_cast<int>((i + 1) * 16), 8 * 8, 16});
name_texts[i].set_parent_rect({UI_POS_X(0), static_cast<int>((i + 1) * 16), 8 * 8, 16});
addr_texts[i].set_parent_rect({9 * 8, static_cast<int>((i + 1) * 16), 4 * 8, 16});
hex_texts[i].set_parent_rect({14 * 8, static_cast<int>((i + 1) * 16), 6 * 8, 16});
value_texts[i].set_parent_rect({21 * 8, static_cast<int>((i + 1) * 16), 10 * 8, 16});
@@ -24,18 +24,18 @@ class BatteryCapacityView : public View {
static RegisterEntry get_entry(size_t index);
Labels labels{
{{0 * 8, 0 * 16}, "Reg", Theme::getInstance()->fg_yellow->foreground},
{{9 * 8, 0 * 16}, "Addr", Theme::getInstance()->fg_yellow->foreground},
{{14 * 8, 0 * 16}, "Hex", Theme::getInstance()->fg_yellow->foreground},
{{21 * 8, 0 * 16}, "Value", Theme::getInstance()->fg_yellow->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Reg", Theme::getInstance()->fg_yellow->foreground},
{{9 * 8, UI_POS_Y(0)}, "Addr", Theme::getInstance()->fg_yellow->foreground},
{{14 * 8, UI_POS_Y(0)}, "Hex", Theme::getInstance()->fg_yellow->foreground},
{{21 * 8, UI_POS_Y(0)}, "Value", Theme::getInstance()->fg_yellow->foreground},
};
std::array<Text, 16> name_texts = {};
std::array<Text, 16> addr_texts = {};
std::array<Text, 16> hex_texts = {};
std::array<Text, 16> value_texts = {};
Text page_text{{144, 284, 80, 16}, "Page 1/1"};
Button button_done{{16, 280, 96, 24}, "Done"};
Text page_text{{144, UI_POS_Y_BOTTOM(3), 80, 16}, "Page 1/1"};
Button button_done{{16, UI_POS_Y_BOTTOM(3), 96, 24}, "Done"};
void update_values();
void populate_page(int start_index);
+6 -2
View File
@@ -103,7 +103,8 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
&text_info_line_8,
&text_info_line_9,
&text_info_line_10,
&text_info_line_11});
&text_info_line_11,
&text_info_line_12});
}
void DfuMenu2::paint(Painter& painter) {
@@ -118,9 +119,12 @@ void DfuMenu2::paint(Painter& painter) {
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));
text_info_line_12.set(std::string("M") + (radio::get_mixer_invert() ? "Y" : "N") +
" B" + (radio::get_baseband_invert() ? "Y" : "N") +
" IQ" + ((radio::get_mixer_invert() ^ radio::get_baseband_invert()) ? "Y" : "N"));
constexpr auto margin = 5;
constexpr auto lines = 11 + 2;
constexpr auto lines = 12 + 2;
painter.fill_rectangle(
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
@@ -96,6 +96,7 @@ class DfuMenu2 : public View {
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Theme::getInstance()->fg_darkcyan->foreground},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Theme::getInstance()->fg_darkcyan->foreground},
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Theme::getInstance()->fg_darkcyan->foreground},
{{5 * CHARACTER_WIDTH, 16 * LINE_HEIGHT}, "IQ Inv:", Theme::getInstance()->fg_darkcyan->foreground},
};
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
@@ -109,6 +110,7 @@ class DfuMenu2 : public View {
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}, ""};
Text text_info_line_12{{(14 + 1) * CHARACTER_WIDTH, 16 * LINE_HEIGHT, (10 - 1) * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
} /* namespace ui */
+2 -2
View File
@@ -216,10 +216,10 @@ class EncodersView : public View {
"Ready"};
ProgressBar progressbar{
{2 * 8, 13 * 16 + 20, 208, 16}};
{2 * 8, 13 * 16 + 20, UI_POS_WIDTH_REMAINING(4), 16}};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
50000,
9};
+77 -95
View File
@@ -43,6 +43,7 @@ namespace ui {
static const fs::path txt_ext{u".TXT"};
static const fs::path ppl_ext{u".PPL"};
static const fs::path c8_ext{u".C8"};
static const fs::path cu8_ext{u".CU8"};
static const fs::path c16_ext{u".C16"};
static const fs::path cxx_ext{u".C*"};
static const fs::path png_ext{u".PNG"};
@@ -88,8 +89,11 @@ fs::path get_partner_file(fs::path path) {
path.replace_extension(txt_ext);
else if (path_iequal(ext, txt_ext)) {
path.replace_extension(c8_ext);
if (!fs::file_exists(path))
path.replace_extension(cu8_ext);
if (!fs::file_exists(path))
path.replace_extension(c16_ext);
} else
return {};
@@ -228,6 +232,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
// Collect all entries first
std::list<fileman_entry> all_entries;
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
// Hide files starting with '.' (hidden / tmp).
if (!show_hidden_files && is_hidden_file(entry.path()))
@@ -235,36 +241,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
if (fs::is_regular_file(entry.status())) {
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
} else if (fs::is_directory(entry.status())) {
insert_sorted(entry_list, {entry.path().string(), 0, true});
insert_sorted(all_entries, {entry.path().string(), 0, true});
}
}
// paginating
auto list_size = entry_list.size();
nb_pages = 1 + (list_size / items_per_page);
size_t start = pagination * items_per_page;
size_t stop = start + items_per_page;
if (list_size > start) {
if (list_size < stop)
stop = list_size;
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
// Calculate pagination
nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
if (nb_pages == 0) nb_pages = 1;
size_t start_idx = pagination * items_per_page;
size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
// Add "parent" directory if not at the root and on first page
if (!dir_path.empty() && pagination == 0) {
entry_list.push_back({parent_dir_path.string(), 0, true});
}
// Add "parent" directory if not at the root.
if (!dir_path.empty() && pagination == 0)
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
// add next page
if (list_size > start + items_per_page) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
// add prev page
// Add prev page navigation if not on first page
if (pagination > 0) {
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
}
// Add entries for current page
auto it = all_entries.begin();
std::advance(it, start_idx);
for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
entry_list.push_back(*it);
}
// Add next page navigation if not on last page
if (end_idx < all_entries.size()) {
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
}
}
@@ -288,6 +298,7 @@ FileManBaseView::FileManBaseView(
std::string filter)
: nav_{nav},
extension_filter{filter} {
max_filename_length = screen_width / 8 - 10;
add_children({&labels,
&text_current,
&button_exit});
@@ -320,30 +331,49 @@ void FileManBaseView::focus() {
} else {
menu_view.focus();
}
// Set menu to the correct page and select the correct item
menu_view.set_highlighted(prev_highlight % items_per_page);
}
// Push directory - store the global index (page * items_per_page + local_index)
void FileManBaseView::push_dir(const fs::path& path) {
if (path == parent_dir_path) {
pop_dir();
} else {
// Save global index (combines page number and item position)
saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
current_path /= path;
saved_index_stack.push_back(menu_view.highlighted_index());
menu_view.set_highlighted(0);
reload_current(true);
reload_current(true); // Reset pagination when entering new directory
}
}
void FileManBaseView::pop_dir() {
if (saved_index_stack.empty())
if (current_path.empty()) {
return;
}
// Move to parent directory
current_path = current_path.parent_path();
reload_current(true);
menu_view.set_highlighted(saved_index_stack.back());
saved_index_stack.pop_back();
// Restore the previous global index if available
if (!saved_index_stack.empty()) {
uint32_t global_index = saved_index_stack.back();
saved_index_stack.pop_back();
// Calculate pagination from global index
pagination = global_index / items_per_page;
// Calculate local index within the page
prev_highlight = global_index % items_per_page;
restoring_navigation = true;
}
reload_current(false); // Important: don't reset pagination
}
std::string get_extension(std::string t) {
std::string FileManBaseView::get_extension(const std::string& t) const {
const auto index = t.find_last_of(u'.');
if (index == t.npos) {
return {};
@@ -360,6 +390,7 @@ std::string get_stem(std::string t) {
return t.substr(0, index);
}
}
std::string get_filename(std::string _s) {
const auto index = _s.find_last_of("/");
if (index == _s.npos) {
@@ -368,16 +399,17 @@ std::string get_filename(std::string _s) {
return _s.substr(index + 1);
}
}
void FileManBaseView::refresh_list() {
if (on_refresh_widgets)
on_refresh_widgets(false);
prev_highlight = menu_view.highlighted_index();
if (!restoring_navigation) {
prev_highlight = menu_view.highlighted_index();
}
menu_view.clear();
for (const auto& entry : entry_list) {
auto entry_name = std::string{entry.path.length() <= 20 ? entry.path : entry.path.substr(0, 20)};
if (entry.is_directory) {
std::string size_str{};
if (entry.path == str_next || entry.path == str_back) {
@@ -386,8 +418,10 @@ void FileManBaseView::refresh_list() {
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
}
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
// Do not use spaces or padding here. The menu will handle the alignment.
menu_view.add_item(
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
{entry.path + "\t" + size_str,
Theme::getInstance()->fg_yellow->foreground,
&bitmap_icon_dir,
[this](KeyEvent key) {
@@ -399,8 +433,9 @@ void FileManBaseView::refresh_list() {
const auto& assoc = get_assoc(get_extension(entry.path));
auto size_str = to_string_file_size(entry.size);
// CRITICAL: Use "\t" (Tab) to separate the path and the size!
menu_view.add_item(
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
{entry.path + "\t" + size_str,
assoc.color,
assoc.icon,
[this](KeyEvent key) {
@@ -411,10 +446,14 @@ void FileManBaseView::refresh_list() {
}
menu_view.set_highlighted(prev_highlight);
restoring_navigation = false;
}
void FileManBaseView::reload_current(bool reset_pagination) {
if (reset_pagination) pagination = 0;
// Only reset pagination if explicitly requested
if (reset_pagination) {
pagination = 0;
}
load_directory_contents(current_path);
refresh_list();
}
@@ -455,7 +494,7 @@ FileLoadView::FileLoadView(
add_children({&menu_view});
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, screen_width, 29 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(4.5)});
refresh_list();
@@ -489,63 +528,6 @@ void FileLoadView::refresh_widgets(const bool) {
set_dirty();
}
/* FileSaveView **************************************************************/
/*
FileSaveView::FileSaveView(
NavigationView& nav,
const fs::path& path,
const fs::path& file
) : nav_{ nav },
path_{ path },
file_{ file }
{
add_children({
&text_path,
&button_edit_path,
&text_name,
&button_edit_name,
&button_save,
&button_cancel,
});
button_edit_path.on_select = [this](Button&) {
buffer_ = path_.string();
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string&) {
path_ = buffer_;
refresh_widgets();
});
};
button_edit_name.on_select = [this](Button&) {
buffer_ = file_.string();
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string&) {
file_ = buffer_;
refresh_widgets();
});
};
button_save.on_select = [this](Button&) {
if (on_save)
on_save(path_ / file_);
else
nav_.pop();
};
button_cancel.on_select = [this](Button&) {
nav_.pop();
};
refresh_widgets();
}
void FileSaveView::refresh_widgets() {
text_path.set(truncate(path_, 30));
text_name.set(truncate(file_, 30));
set_dirty();
}
*/
/* FileManagerView ***********************************************************/
void FileManagerView::refresh_widgets(const bool v) {
+20 -17
View File
@@ -64,7 +64,8 @@ class FileManBaseView : public View {
uint32_t prev_highlight = 0;
uint8_t pagination = 0;
uint8_t nb_pages = 1;
static constexpr size_t max_filename_length = 20;
bool restoring_navigation = false;
size_t max_filename_length = 20;
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
static constexpr size_t items_per_page = 20;
@@ -79,6 +80,7 @@ class FileManBaseView : public View {
{u".PNG", &bitmap_icon_file_image, ui::Color::green()},
{u".BMP", &bitmap_icon_file_image, ui::Color::green()},
{u".C8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".CU8", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".C16", &bitmap_icon_file_iq, ui::Color::dark_cyan()},
{u".WAV", &bitmap_icon_file_wav, ui::Color::dark_magenta()},
{u".PPL", &bitmap_icon_file_iq, ui::Color::white()}, // Playlist/Replay
@@ -96,6 +98,7 @@ class FileManBaseView : public View {
void load_directory_contents(const std::filesystem::path& dir_path);
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
std::string get_extension(const std::string& t) const;
void copy_waterfall(std::filesystem::path path);
NavigationView& nav_;
@@ -120,16 +123,16 @@ class FileManBaseView : public View {
{{0, 0}, "Path:", Theme::getInstance()->fg_light->foreground}};
Text text_current{
{6 * 8, 0 * 8, 24 * 8, 16},
{UI_POS_X(6), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(6), UI_POS_HEIGHT(1)},
"",
};
MenuView menu_view{
{0, 2 * 8, screen_width, 26 * 8},
{0, UI_POS_Y(1), UI_POS_MAXWIDTH, UI_POS_HEIGHT_REMAINING(7)},
true};
Button button_exit{
{22 * 8, 34 * 8, 8 * 8, 32},
{UI_POS_X_RIGHT(8), UI_POS_Y_BOTTOM(3), 8 * 8, UI_POS_HEIGHT(2)},
"Exit"};
};
@@ -228,66 +231,66 @@ class FileManagerView : public FileManBaseView {
bool selected_is_valid() const;
Text text_date{
{0 * 8, 26 * 8, 28 * 8, 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(6), 28 * 8, UI_POS_HEIGHT(1)},
""};
NewButton button_rename{
{0 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(0), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_rename,
Theme::getInstance()->fg_blue->foreground};
NewButton button_delete{
{9 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(9), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_trash,
Theme::getInstance()->fg_red->foreground};
NewButton button_clean{
{13 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(13), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_clean,
Theme::getInstance()->fg_red->foreground};
NewButton button_cut{
{9 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(9), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_cut,
Theme::getInstance()->fg_dark->foreground};
NewButton button_copy{
{13 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(13), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_copy,
Theme::getInstance()->fg_dark->foreground};
NewButton button_paste{
{17 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(17), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_paste,
Theme::getInstance()->fg_dark->foreground};
NewButton button_new_dir{
{22 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(22), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_new_dir,
Theme::getInstance()->fg_green->foreground};
NewButton button_new_file{
{26 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(26), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_new_file,
Theme::getInstance()->fg_green->foreground};
NewButton button_open_notepad{
{0 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_notepad,
Theme::getInstance()->fg_orange->foreground};
NewButton button_rename_timestamp{
{4 * 8, 29 * 8, 4 * 8, 32},
{UI_POS_X(4), UI_POS_Y_BOTTOM(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_options_datetime,
Theme::getInstance()->fg_blue->foreground,
@@ -295,13 +298,13 @@ class FileManagerView : public FileManBaseView {
NewButton button_open_iq_trim{
{4 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(4), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_trim,
Theme::getInstance()->fg_orange->foreground};
NewButton button_show_hidden_files{
{17 * 8, 34 * 8, 4 * 8, 32},
{UI_POS_X(17), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_hide,
Theme::getInstance()->fg_dark->foreground};
+10 -4
View File
@@ -32,7 +32,7 @@ static const char* hackrf_magic = "HACKRFFW";
#define FIRST_CHECKSUM_NIGHTLY 240125
Thread* FlashUtilityView::thread{nullptr};
static constexpr size_t max_filename_length = 26;
static constexpr size_t max_filename_length = 60; // max length of filename
bool valid_firmware_file(std::filesystem::path::string_type path) {
File firmware_file;
@@ -45,10 +45,14 @@ bool valid_firmware_file(std::filesystem::path::string_type path) {
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
auto result = firmware_file.open(path.c_str());
if (!result.is_valid()) {
uint64_t file_size = firmware_file.size();
if (file_size > FLASH_ROM_SIZE) {
// Firmware file is larger than the flash size for this device
return false;
}
checksum = 0;
for (uint32_t offset = 0; offset < FLASH_ROM_SIZE; offset += sizeof(read_buffer)) {
for (uint64_t offset = 0; offset < FLASH_ROM_SIZE && offset < file_size; offset += sizeof(read_buffer)) {
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
// File was smaller than 1MB:
// If version is such that the file SHOULD have been 1MB, call it a checksum error (otherwise say it's OK).
@@ -147,6 +151,7 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
ui::Painter painter;
menu_view.hidden(true);
if (endsWith(path, u".tar")) {
// extract, then update
path = extract_tar(u'/' + path, painter).native();
@@ -156,13 +161,14 @@ bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, Theme::getInstance()->bg_darkest->background);
painter.draw_string({0, 60}, *Theme::getInstance()->fg_red, "BAD FIRMWARE FILE OR W/R ERR");
chThdSleepMilliseconds(5000);
menu_view.hidden(false);
return false;
}
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
Theme::getInstance()->bg_darkest->background);
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15 seconds.");
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15-45 sec.");
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LED RX");
painter.draw_string({12, 84}, this->nav_.style(), "is on and TX is flashing.");
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
@@ -32,7 +32,7 @@
#include "untar.hpp"
#include <cstdint>
#define FLASH_ROM_SIZE 1048576
#define FLASH_ROM_SIZE (FLASH_SIZE_MB * 1024 * 1024)
#define FLASH_STARTING_ADDRESS 0x00000000
#define FLASH_EXPECTED_CHECKSUM 0x00000000
#define FLASH_CHECKSUM_ERROR 0xFFFFFFFF
@@ -60,7 +60,7 @@ class FlashUtilityView : public View {
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, screen_width, 26 * 8},
{0, UI_POS_Y(1), screen_width, UI_POS_HEIGHT_REMAINING(2)},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
+23 -23
View File
@@ -65,14 +65,14 @@ class FreqManBaseView : public View {
OptionsField options_category{
{3 * 8, 2},
20 /* length */,
19 /* length */,
{}};
FreqManUIList freqlist_view{
{0, 3 * 8, screen_width, 12 * 16 + 2 /* 2 Keeps text out of border. */}};
Button button_exit{
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X_RIGHT(14), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Exit"};
protected:
@@ -97,14 +97,14 @@ class FrequencySaveView : public FreqManBaseView {
0};
Labels labels{
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
{{UI_POS_X(0), 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
TextField field_description{
{0 * 8, 7 * 16, screen_width, 1 * 16},
{UI_POS_X(0), 7 * 16, screen_width, 1 * 16},
""};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(14), UI_POS_HEIGHT(2)},
"Save"};
};
@@ -134,44 +134,44 @@ class FrequencyManagerView : public FreqManBaseView {
void on_del_entry();
NewButton button_add_category{
{23 * 8, 0 * 16, 7 * 4, 20},
{UI_POS_X_RIGHT(8), UI_POS_Y(0), 7 * 4, 20},
{},
&bitmap_icon_new_file,
Theme::getInstance()->bg_darkest->foreground,
true};
NewButton button_del_category{
{26 * 8 + 4, 0 * 16, 7 * 4, 20},
{UI_POS_X_RIGHT(4), UI_POS_Y(0), 7 * 4, 20},
{},
&bitmap_icon_trash,
Theme::getInstance()->fg_red->foreground,
true};
Button button_edit_entry{
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
{UI_POS_X(0), UI_POS_Y_BOTTOM(6.5) + 4, 15 * 8, 1 * 16 + 4},
"Edit"};
Rectangle rect_padding{
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
{15 * 8, UI_POS_Y_BOTTOM(6.5), 15 * 8, 1 * 16 + 4},
Theme::getInstance()->fg_medium->background};
Button button_edit_freq{
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(5), 15 * 8, 2 * 16},
"Frequency"};
Button button_edit_desc{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
"Description"};
NewButton button_add_entry{
{15 * 8, 15 * 16, 7 * 8 + 4, 2 * 16},
{15 * 8, UI_POS_Y_BOTTOM(5), 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_add,
Theme::getInstance()->bg_darkest->foreground,
true};
NewButton button_del_entry{
{22 * 8 + 4, 15 * 16, 7 * 8 + 4, 2 * 16},
{22 * 8 + 4, UI_POS_Y_BOTTOM(5), 7 * 8 + 4, 2 * 16},
{},
&bitmap_icon_delete,
Theme::getInstance()->fg_red->foreground,
@@ -201,14 +201,14 @@ class FrequencyEditView : public View {
Labels labels{
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Theme::getInstance()->bg_darkest->foreground},
{{0 * 8, 3 * 16}, "Entry Type :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Frequency A:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Frequency B:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Modulation :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Bandwidth :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Step :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 9 * 16}, "Tone Freq :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 10 * 16}, "Description:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Entry Type :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 4 * 16}, "Frequency A:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 5 * 16}, "Frequency B:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 6 * 16}, "Modulation :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "Bandwidth :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "Step :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 9 * 16}, "Tone Freq :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 10 * 16}, "Description:", Theme::getInstance()->fg_light->foreground},
};
OptionsField field_type{
@@ -243,11 +243,11 @@ class FrequencyEditView : public View {
{}};
Button button_save{
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
"Save"};
Button button_cancel{
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
{UI_POS_X_RIGHT(15), UI_POS_Y_BOTTOM(3), 15 * 8, 2 * 16},
"Cancel"};
};
+2 -1
View File
@@ -171,7 +171,8 @@ void IQTrimView::update_range_controls(iq::TrimRange trim_range) {
}
void IQTrimView::profile_capture() {
power_buckets_ = {};
power_buckets_.clear();
power_buckets_.resize(screen_width);
iq::PowerBuckets buckets{
.p = power_buckets_.data(),
.size = power_buckets_.size()};
+11 -11
View File
@@ -54,7 +54,7 @@ class TrimProgressUI {
}
void clear() {
p.fill_rectangle({0 * 8, 4 * 16, screen_width, 3 * 16}, Theme::getInstance()->bg_darkest->background);
p.fill_rectangle({UI_POS_X(0), 4 * 16, screen_width, 3 * 16}, Theme::getInstance()->bg_darkest->background);
}
auto get_callback() {
@@ -100,22 +100,22 @@ class IQTrimView : public View {
TrimProgressUI progress_ui{};
Labels labels{
{{0 * 8, 0 * 16}, "Capture File:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Start :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "End :", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Samples:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 9 * 16}, "Max Pwr:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 10 * 16}, "Cutoff :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Capture File:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 6 * 16}, "Start :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "End :", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "Samples:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 9 * 16}, "Max Pwr:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 10 * 16}, "Cutoff :", Theme::getInstance()->fg_light->foreground},
{{12 * 8, 10 * 16}, "%", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 12 * 16}, "Amplify:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 12 * 16}, "Amplify:", Theme::getInstance()->fg_light->foreground},
{{10 * 8, 12 * 16}, "x", Theme::getInstance()->fg_light->foreground},
};
TextField field_path{
{0 * 8, 1 * 16, screen_width, 1 * 16},
{UI_POS_X(0), 1 * 16, screen_width, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
Point pos_lines{UI_POS_X(0), 4 * 16};
Dim height_lines{2 * 16};
NumberField field_start{
@@ -155,7 +155,7 @@ class IQTrimView : public View {
' '};
Button button_trim{
{20 * 8, 16 * 16, 8 * 8, 2 * 16},
{UI_POS_X_CENTER(8), UI_POS_Y_BOTTOM(3), 8 * 8, 2 * 16},
"Trim"};
};
@@ -24,6 +24,7 @@
#include "ui_looking_glass_app.hpp"
#include "convert.hpp"
#include "file_reader.hpp"
#include "file.hpp"
#include "string_format.hpp"
#include "audio.hpp"
#include "file_path.hpp"
@@ -31,6 +32,7 @@
using namespace portapack;
namespace ui {
void GlassView::focus() {
range_presets.focus();
}
@@ -44,7 +46,7 @@ GlassView::~GlassView() {
// Function to map the value from one range to another
int32_t GlassView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
return toLow + ((value - fromLow) * (toHigh - toLow) + (fromHigh - fromLow) / 2) / (fromHigh - fromLow);
}
void GlassView::update_display_beep() {
@@ -69,7 +71,7 @@ void GlassView::manage_beep_audio() {
}
}
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power) {
void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint16_t bin, uint8_t& max_power) {
if (mode == LOOKING_GLASS_SINGLEPASS) {
// <20MHz spectrum mode
if (bin < 120) {
@@ -91,7 +93,7 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
}
}
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency GlassView::get_freq_from_bin_pos(uint16_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by screen_width(240)/2 => 120
@@ -101,7 +103,14 @@ rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
return freq_at_pos;
}
void GlassView::on_marker_change() {
void GlassView::on_marker_change(int8_t delta) {
if ((marker_pixel_index + delta) < 0)
marker_pixel_index = marker_pixel_index + delta + screen_width;
else if ((marker_pixel_index + delta) > screen_width)
marker_pixel_index = marker_pixel_index + delta - screen_width;
else
marker_pixel_index = marker_pixel_index + delta;
marker = get_freq_from_bin_pos(marker_pixel_index);
field_marker.set_text(to_string_short_freq(marker));
plot_marker(marker_pixel_index); // Refresh marker on screen
@@ -121,8 +130,9 @@ void GlassView::reset_live_view() {
max_freq_power = -1000;
// Clear screen in peak mode.
const int spectrum_height = screen_height - spectrum_y;
if (live_frequency_view == 2)
display.fill_rectangle({{0, 108 + 16}, {screen_width, screen_height - (108 + 16)}}, {0, 0, 0});
display.fill_rectangle({{0, spectrum_y}, {screen_width, spectrum_height}}, {0, 0, 0});
}
void GlassView::add_spectrum_pixel(uint8_t power) {
@@ -137,11 +147,9 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
constexpr float rssi_voltage_min = 0.4;
constexpr float rssi_voltage_max = 2.2;
constexpr float adc_voltage_max = 3.3;
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
constexpr int raw_delta = raw_max - raw_min;
const range_t<int> y_max_range{0, screen_height - (108 + 16)};
constexpr int raw_min = 0.5f + rssi_sample_range * rssi_voltage_min / adc_voltage_max;
constexpr int raw_max = 0.5f + rssi_sample_range * rssi_voltage_max / adc_voltage_max;
const int spectrum_height = screen_height - spectrum_y;
// drawing and keeping track of max freq
for (uint16_t xpos = 0; xpos < screen_width; xpos++) {
// save max powerwull freq
@@ -149,17 +157,21 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
max_freq_power = spectrum_data[xpos];
max_freq_hold = get_freq_from_bin_pos(xpos);
}
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (screen_height - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
int16_t point = map(spectrum_data[xpos], 0, 255, 0, spectrum_height);
// clear if not in peak view
if (live_frequency_view != 2) {
display.fill_rectangle({{xpos, 108 + 16}, {1, screen_height - point}}, {0, 0, 0});
display.fill_rectangle({{xpos, spectrum_y}, {1, screen_height - point}}, {0, 0, 0});
}
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, gradient.lut[spectrum_data[xpos]]);
}
// indicate RSSI min and max power
if (show_rssi_guides) {
display.fill_rectangle({{0, screen_height - map(raw_min, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_min]);
display.fill_rectangle({{0, screen_height - map(raw_max, 0, 255, 0, spectrum_height)}, {screen_width, 1}}, -gradient.lut[raw_max]);
}
if (last_max_freq != max_freq_hold) {
last_max_freq = max_freq_hold;
freq_stats.set("MAX HOLD: " + to_string_short_freq(max_freq_hold));
freq_stats.set("MAX: " + to_string_short_freq(max_freq_hold));
}
plot_marker(marker_pixel_index);
} else {
@@ -200,7 +212,7 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
// we actually need screen_width (240) of those bins
for (uint8_t bin = 0; bin < bin_length; bin++) {
for (uint16_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (max_power > range_max_power)
range_max_power = max_power;
@@ -208,7 +220,7 @@ void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
if (bin == 119) {
uint8_t next_max_power = 0;
get_max_power(spectrum, bin + 1, next_max_power);
for (uint8_t it = 0; it < ignore_dc; it++) {
for (uint16_t it = 0; it < ignore_dc; it++) {
uint8_t med_max_power = (max_power + next_max_power) / 2; // due to the way process_bins works we have to keep resetting the color
if (process_bins(&med_max_power) == true)
return; // new line signaled, return
@@ -285,7 +297,7 @@ void GlassView::on_range_changed() {
max_power = 0;
bins_hz_size = 0;
on_marker_change();
on_marker_change(0);
update_range_field();
// set the sample rate and bandwidth
@@ -298,8 +310,8 @@ void GlassView::on_range_changed() {
receiver_model.set_target_frequency(f_center); // tune rx for this slice
}
void GlassView::plot_marker(uint8_t pos) {
uint8_t shift_y = 0;
void GlassView::plot_marker(uint16_t pos) {
uint16_t shift_y = 0;
if (live_frequency_view > 0) // plot one line down when in live view
{
shift_y = 16;
@@ -316,8 +328,8 @@ void GlassView::update_min(int32_t v) {
min_size = search_span;
if (min_size < 1)
min_size = 1;
if (v > 7200 - min_size) {
v = 7200 - min_size;
if (v > LOOKING_GLASS_MAX_FREQ_MHZ - min_size) {
v = LOOKING_GLASS_MAX_FREQ_MHZ - min_size;
}
if (v > (field_frequency_max.value() - min_size))
field_frequency_max.set_value(v + min_size, false);
@@ -336,6 +348,9 @@ void GlassView::update_max(int32_t v) {
if (v < min_size) {
v = min_size;
}
if (v > LOOKING_GLASS_MAX_FREQ_MHZ) {
v = LOOKING_GLASS_MAX_FREQ_MHZ;
}
if (v < (field_frequency_min.value() + min_size))
field_frequency_min.set_value(v - min_size, false);
if (locked_range)
@@ -381,10 +396,12 @@ GlassView::GlassView(
&button_beep_squelch,
&field_marker,
&field_trigger,
&button_marker_minus,
&button_marker_plus,
&button_jump,
&button_rst,
&field_rx_iq_phase_cal,
&freq_stats});
&button_rssi,
&freq_stats, &button_play});
load_presets(); // Load available presets from TXT files (or default).
preset_index = clip<uint8_t>(preset_index, 0, presets_db.size());
@@ -438,6 +455,7 @@ GlassView::GlassView(
freq_stats.hidden(true);
button_jump.hidden(true);
button_rst.hidden(true);
button_rssi.hidden(true);
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
break;
@@ -448,6 +466,7 @@ GlassView::GlassView(
freq_stats.hidden(false);
button_jump.hidden(false);
button_rst.hidden(false);
button_rssi.hidden(false);
break;
case 2: // PEAK
@@ -458,6 +477,7 @@ GlassView::GlassView(
freq_stats.hidden(false);
button_jump.hidden(false);
button_rst.hidden(false);
button_rssi.hidden(false);
break;
}
@@ -491,13 +511,7 @@ GlassView::GlassView(
range_presets.set_selected_index(preset_index);
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
if ((marker_pixel_index + delta) < 0)
marker_pixel_index = marker_pixel_index + delta + screen_width;
else if ((marker_pixel_index + delta) > screen_width)
marker_pixel_index = marker_pixel_index + delta - screen_width;
else
marker_pixel_index = marker_pixel_index + delta;
on_marker_change();
on_marker_change(delta);
};
field_marker.on_select = [this](TextField&) {
@@ -516,6 +530,14 @@ GlassView::GlassView(
update_range_field();
};
button_marker_minus.on_select = [this](Button&) {
on_marker_change(-1);
};
button_marker_plus.on_select = [this](Button&) {
on_marker_change(1);
};
button_jump.on_select = [this](Button&) {
// Launch Audio with peak frequency.
launch_audio(max_freq_hold);
@@ -525,12 +547,30 @@ GlassView::GlassView(
reset_live_view();
};
field_rx_iq_phase_cal.set_range(0, hackrf_r9 ? 63 : 31); // max2839 has 6 bits [0..63], max2837 has 5 bits [0..31]
field_rx_iq_phase_cal.set_value(get_spec_iq_phase_calibration_value()); // using accessor function of AnalogAudioView to read iq_phase_calibration_value from rx_audio.ini
field_rx_iq_phase_cal.on_change = [this](int32_t v) {
set_spec_iq_phase_calibration_value(v); // using accessor function of AnalogAudioView to write inside SPEC submenu, register value to max283x and save it to rx_audio.ini
button_rssi.on_select = [this](Button& button) {
show_rssi_guides = !show_rssi_guides;
if (show_rssi_guides) {
button.set_style(Theme::getInstance()->fg_green);
} else {
button.set_style(Theme::getInstance()->bg_darkest);
}
reset_live_view();
};
button_play.on_select = [this](ImageButton&) {
paused = !paused;
if (paused) {
button_play.set_foreground(Theme::getInstance()->fg_red->foreground);
button_play.set_background(Theme::getInstance()->fg_red->background);
baseband::spectrum_streaming_stop();
button_play.set_bitmap(&bitmap_play);
} else {
button_play.set_foreground(Theme::getInstance()->fg_green->foreground);
button_play.set_background(Theme::getInstance()->fg_green->background);
baseband::spectrum_streaming_start();
button_play.set_bitmap(&bitmap_stop);
}
};
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
@@ -581,44 +621,40 @@ void GlassView::on_freqchg(int64_t freq) {
on_range_changed();
}
uint8_t GlassView::get_spec_iq_phase_calibration_value() { // define accessor functions inside AnalogAudioView to read & write real iq_phase_calibration_value
return iq_phase_calibration_value;
}
void GlassView::set_spec_iq_phase_calibration_value(uint8_t cal_value) { // define accessor functions
iq_phase_calibration_value = cal_value;
radio::set_rx_max283x_iq_phase_calibration(iq_phase_calibration_value);
}
void GlassView::load_presets() {
File presets_file;
auto error = presets_file.open(looking_glass_dir / u"PRESETS.TXT");
presets_db.clear();
// Add the "Manual" entry.
presets_db.push_back({0, 0, "Manual"});
if (!error) {
auto reader = FileLineReader(presets_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue;
scan_root_files(looking_glass_dir, u"*.TXT", [this](const std::filesystem::path& path) {
if (path.empty() || path.native()[0] == u'.')
return;
auto cols = split_string(line, ',');
if (cols.size() != 3)
continue;
File presets_file;
auto error = presets_file.open(looking_glass_dir / path);
if (!error) {
auto reader = FileLineReader(presets_file);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue;
preset_entry entry{};
parse_int(cols[0], entry.min);
parse_int(cols[1], entry.max);
entry.label = trimr(cols[2]);
auto cols = split_string(line, ',');
if (cols.size() != 3)
continue;
if (entry.min == 0 || entry.max == 0 || entry.min >= entry.max)
continue; // Invalid line.
preset_entry entry{};
parse_int(cols[0], entry.min);
parse_int(cols[1], entry.max);
entry.label = trimr(cols[2]);
presets_db.emplace_back(std::move(entry));
if (entry.max == 0 || entry.min >= entry.max)
continue; // Invalid line.
presets_db.emplace_back(std::move(entry));
}
}
}
});
populate_presets();
}
@@ -42,6 +42,7 @@ namespace ui {
#define LOOKING_GLASS_SLICE_WIDTH_MAX 20000000
#define LOOKING_GLASS_MAX_SAMPLERATE 20000000
#define MHZ_DIV 1000000
#define LOOKING_GLASS_MAX_FREQ_MHZ 7250 // HackRF upper tuning limit (band_high top), in MHz
// blanked DC (16 centered bins ignored ) and top left and right (2 bins ignored on each side )
#define LOOKING_GLASS_FASTSCAN 0
@@ -66,9 +67,6 @@ class GlassView : public View {
void on_hide() override;
void focus() override;
uint8_t get_spec_iq_phase_calibration_value();
void set_spec_iq_phase_calibration_value(uint8_t cal_value);
private:
NavigationView& nav_;
Gradient gradient{};
@@ -80,11 +78,10 @@ class GlassView : public View {
uint8_t filter_index = 0; // OFF
uint8_t trigger = 32;
uint8_t mode = LOOKING_GLASS_FASTSCAN;
uint8_t live_frequency_view = 0; // Spectrum
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
int32_t beep_squelch = 20; // range from -100 to +20, >=20 disabled
bool beep_enabled = false; // activate on bip button click
uint8_t live_frequency_view = 0; // Spectrum
uint8_t live_frequency_integrate = 3; // Default (3 * old value + new_value) / 4
int32_t beep_squelch = 20; // range from -100 to +20, >=20 disabled
bool beep_enabled = false; // activate on bip button click
app_settings::SettingsManager settings_{
"rx_glass"sv,
app_settings::Mode::RX,
@@ -97,7 +94,6 @@ class GlassView : public View {
{"scan_mode"sv, &mode},
{"freq_view"sv, &live_frequency_view},
{"freq_integrate"sv, &live_frequency_integrate},
{"iq_phase_calibration"sv, &iq_phase_calibration_value}, // we are saving and restoring that CAL from Settings.
{"beep_squelch"sv, &beep_squelch},
{"beep_enabled"sv, &beep_enabled},
}};
@@ -109,6 +105,7 @@ class GlassView : public View {
};
void on_freqchg(int64_t freq);
// Function to map the value from one range to another
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
std::vector<preset_entry> presets_db{};
void manage_beep_audio();
@@ -116,9 +113,9 @@ class GlassView : public View {
void update_min(int32_t v);
void update_max(int32_t v);
void update_range_field();
void get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint8_t pos);
void on_marker_change();
void get_max_power(const ChannelSpectrum& spectrum, uint16_t bin, uint8_t& max_power);
rf::Frequency get_freq_from_bin_pos(uint16_t pos);
void on_marker_change(int8_t delta);
void retune();
bool process_bins(uint8_t* powerlevel);
void on_channel_spectrum(const ChannelSpectrum& spectrum);
@@ -128,17 +125,19 @@ class GlassView : public View {
void on_range_changed();
void reset_live_view();
void add_spectrum_pixel(uint8_t power);
void plot_marker(uint8_t pos);
void plot_marker(uint16_t pos);
void load_presets();
void populate_presets();
void launch_audio(rf::Frequency center_freq);
bool paused{false};
rf::Frequency search_span{0};
rf::Frequency f_center{0};
rf::Frequency f_center_ini{0};
rf::Frequency marker{0};
uint8_t marker_pixel_index{0};
uint16_t marker_pixel_index{0};
rf::Frequency marker_pixel_step{0};
// Size of one spectrum bin in Hz.
@@ -168,41 +167,44 @@ class GlassView : public View {
uint8_t bin_length = screen_width;
uint8_t offset = 0;
uint8_t ignore_dc = 0;
bool show_rssi_guides = false;
// start of spectrum area
static constexpr int spectrum_y = (108 + 16);
Labels labels{
{{0, 0 * 16}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
{{0, 1 * 16}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
{{0, 2 * 16}, "P:", Theme::getInstance()->fg_light->foreground},
{{0, 3 * 16}, "MARKER: MHz RXIQCAL", Theme::getInstance()->fg_light->foreground},
//{{0, 4 * 16}, "RES: STEPS:", Theme::getInstance()->fg_light->foreground}};
{{0, 4 * 16}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
{{0, UI_POS_Y(0)}, "MIN: MAX: LNA VGA ", Theme::getInstance()->fg_light->foreground},
{{0, UI_POS_Y(1)}, "RANGE: FILTER: AMP:", Theme::getInstance()->fg_light->foreground},
{{0, UI_POS_Y(2)}, "P:", Theme::getInstance()->fg_light->foreground},
{{0, UI_POS_Y(3)}, "MARKER( / ): MHz", Theme::getInstance()->fg_light->foreground},
{{0, UI_POS_Y(4)}, "RES: VOL:", Theme::getInstance()->fg_light->foreground}};
NumberField field_frequency_min{
{4 * 8, 0 * 16},
{UI_POS_X(4), UI_POS_Y(0)},
4,
{0, 7199},
{0, LOOKING_GLASS_MAX_FREQ_MHZ - 1},
1, // number of steps by encoder delta
' '};
NumberField field_frequency_max{
{13 * 8, 0 * 16},
{UI_POS_X(13), UI_POS_Y(0)},
4,
{1, 7200},
{1, LOOKING_GLASS_MAX_FREQ_MHZ},
1, // number of steps by encoder delta
' '};
LNAGainField field_lna{
{21 * 8, 0 * 16}};
{UI_POS_X(21), UI_POS_Y(0)}};
VGAGainField field_vga{
{27 * 8, 0 * 16}};
{UI_POS_X(27), UI_POS_Y(0)}};
TextField field_range{
{6 * 8, 1 * 16, 6 * 8, 16},
{UI_POS_X(6), UI_POS_Y(1), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)},
""};
OptionsField filter_config{
{20 * 8, 1 * 16},
{UI_POS_X(20), UI_POS_Y(1)},
4,
{
{"OFF ", 0},
@@ -211,41 +213,47 @@ class GlassView : public View {
}};
RFAmpField field_rf_amp{
{28 * 8, 1 * 16}};
{UI_POS_X(28), UI_POS_Y(1)}};
OptionsField range_presets{
{2 * 8, 2 * 16},
{UI_POS_X(2), UI_POS_Y(2)},
20,
{}};
ButtonWithEncoder button_beep_squelch{
{screen_width - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
{UI_POS_X_RIGHT(8), UI_POS_Y(2.25), UI_POS_WIDTH(8), UI_POS_HEIGHT(0.5)},
""};
ImageButton button_play{
{UI_POS_X_RIGHT(2), UI_POS_Y(3.25) - 2, UI_POS_WIDTH(2), UI_POS_HEIGHT(0.5)},
&bitmap_stop,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
TextField field_marker{
{7 * 8, 3 * 16, 9 * 8, 16},
{UI_POS_X(12), UI_POS_Y(3), UI_POS_WIDTH(9), UI_POS_HEIGHT(1)},
""};
NumberField field_rx_iq_phase_cal{
{28 * 8, 3 * 16},
2,
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
1,
' ',
};
Button button_marker_minus{
{UI_POS_X(7), UI_POS_Y(3.25), UI_POS_WIDTH(1), UI_POS_HEIGHT(0.5)},
"-"};
Button button_marker_plus{
{UI_POS_X(9), UI_POS_Y(3.25), UI_POS_WIDTH(1), UI_POS_HEIGHT(0.5)},
"+"};
NumberField field_trigger{
{4 * 8, 4 * 16},
{UI_POS_X(4), UI_POS_Y(4)},
3,
{2, 128},
2,
' '};
AudioVolumeField field_volume{
{13 * 8, 4 * 16}};
{UI_POS_X(13), UI_POS_Y(4)}};
/*OptionsField steps_config{
{13 * 8, 4 * 16},
{UI_POS_X(13), UI_POS_Y(4)},
3,
{
{"1", 1},
@@ -257,7 +265,7 @@ class GlassView : public View {
}};*/
OptionsField scan_type{
{17 * 8, 4 * 16},
{UI_POS_X(17), UI_POS_Y(4)},
2,
{
{"F-", LOOKING_GLASS_FASTSCAN},
@@ -265,7 +273,7 @@ class GlassView : public View {
}};
OptionsField view_config{
{19 * 8, 4 * 16},
{UI_POS_X(19), UI_POS_Y(4)},
7,
{
{"SPCTR-V", 0},
@@ -274,7 +282,7 @@ class GlassView : public View {
}};
OptionsField level_integration{
{27 * 8, 4 * 16},
{UI_POS_X(27), UI_POS_Y(4)},
2,
{
{"x0", 0},
@@ -290,15 +298,19 @@ class GlassView : public View {
}};
Button button_jump{
{screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
{UI_POS_X_RIGHT(4), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
"JMP"};
Button button_rst{
{screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
{UI_POS_X_RIGHT(9), UI_POS_Y(5), UI_POS_WIDTH(4), UI_POS_HEIGHT(1)},
"RST"};
Button button_rssi{
{UI_POS_X_RIGHT(15), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)},
"RSSI"};
Text freq_stats{
{0 * 8, 5 * 16, screen_width - 10 * 8, 8},
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH(14), UI_POS_HEIGHT(1)},
""};
MessageHandlerRegistration message_handler_spectrum_config{
+11
View File
@@ -350,6 +350,12 @@ MicTXView::MicTXView(
&tx_button,
&tx_icon});
// disable key repeat on select
initial_switch_config_ = get_switches_repeat_config();
SwitchesState config = initial_switch_config_;
config[toUType(Switch::Sel)] = false;
set_switches_repeat_config(config);
set_rxbw_options();
set_rxbw_defaults(settings_.loaded());
@@ -446,6 +452,8 @@ MicTXView::MicTXView(
field_frequency.set_value(tx_frequency);
// TODO: would be nice if frequency step was configurable in this app
// but now, make it a bit lower
receiver_model.set_frequency_step(12'500); // set freq step to 12.5kHz
field_frequency.set_step(receiver_model.frequency_step());
// WARNING: transmitter_model.set_target_frequency() and receiver_model.set_target_frequency() both update the same tuning freq, but one has an offset!
@@ -638,6 +646,9 @@ MicTXView::MicTXView(
}
MicTXView::~MicTXView() {
// restore select key repeat mode
set_switches_repeat_config(initial_switch_config_);
audio::input::stop();
if (rx_enabled) { // Also turn off both (audio rx if enabled, and disable mic_loop to HP)
rxaudio(false);
+6 -2
View File
@@ -90,6 +90,9 @@ class MicTXView : public View {
sampling_rate /* sampling rate */
};
// structure used to control key repeat
SwitchesState initial_switch_config_{};
enum Mic_Modulation : uint32_t {
MIC_MOD_NFM = 0,
MIC_MOD_WFM = 1,
@@ -170,7 +173,7 @@ class MicTXView : public View {
{{17 * 8, 1 * 8}, "ALC", Theme::getInstance()->fg_light->foreground}};
VuMeter vumeter{
{0 * 8, 1 * 8, 2 * 8, 33 * 8},
{UI_POS_X(0), 1 * 8, 2 * 8, 33 * 8},
12,
true};
@@ -301,7 +304,8 @@ class MicTXView : public View {
{
{" 8k5 ", 0}, // Initial dynamic values when we start Mic App.
{" 11k ", 1},
{" 16k ", 2},
{" 12k5 ", 2},
{" 16k ", 3},
}};
NumberField field_squelch{
+1 -1
View File
@@ -104,7 +104,7 @@ class ModemSetupView : public View {
"SET"};
Button button_save{
{9 * 8, 250, 96, 40},
{UI_POS_X_CENTER(12), 250, UI_POS_WIDTH(12), 40},
"Save"};
};
@@ -387,7 +387,6 @@ PlaylistView::PlaylistView(
&button_next,
&waterfall,
});
ensure_directory(playlist_dir);
waterfall.show_audio_spectrum_view(false);
+7 -7
View File
@@ -107,24 +107,24 @@ class PlaylistView : public View {
void handle_replay_thread_done(uint32_t return_code);
Text text_filename{
{0 * 8, 0 * 16, screen_width, 16}};
{UI_POS_X(0), UI_POS_Y(0), screen_width, 16}};
FrequencyField field_frequency{
{0 * 8, 1 * 16}};
{UI_POS_X(0), 1 * 16}};
Text text_sample_rate{
{10 * 8, 1 * 16, 7 * 8, 16}};
ProgressBar progressbar_track{
{18 * 8, 1 * 16, 12 * 8, 8 + 1}};
{18 * 8, 1 * 16, UI_POS_WIDTH_REMAINING(19), 8 + 1}};
// (-1) to overlap with progressbar_track so there's
// only 1 pixel between them instead of 2.
ProgressBar progressbar_transmit{
{18 * 8, 3 * 8 - 1, 12 * 8, 8}};
{18 * 8, 3 * 8 - 1, UI_POS_WIDTH_REMAINING(19), 8}};
Text text_duration{
{0 * 8, 2 * 16, 5 * 8, 16}};
{UI_POS_X(0), 2 * 16, 5 * 8, 16}};
// TODO: delay duration field.
@@ -133,7 +133,7 @@ class PlaylistView : public View {
/*short_ui*/ true};
Checkbox check_loop{
{21 * 8, 2 * 16},
{UI_POS_X_RIGHT(9), 2 * 16},
4,
"Loop",
true};
@@ -145,7 +145,7 @@ class PlaylistView : public View {
Theme::getInstance()->fg_green->background};
Text text_track{
{0 * 8, 3 * 16, screen_width, 16}};
{UI_POS_X(0), 3 * 16, screen_width, 16}};
NewButton button_prev{
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
+5 -5
View File
@@ -82,7 +82,7 @@ class RDSRadioTextView : public OptionTabView {
{1 * 8, 4 * 16, 28 * 8, 16},
"-"};
Button button_set{
{88, 6 * 16, 64, 32},
{UI_POS_X_CENTER(8), 6 * 16, UI_POS_WIDTH(8), 32},
"Set"};
};
@@ -92,7 +92,7 @@ class RDSDateTimeView : public OptionTabView {
private:
Labels labels{
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
{{UI_POS_X_CENTER(19), 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
};
class RDSAudioView : public OptionTabView {
@@ -101,7 +101,7 @@ class RDSAudioView : public OptionTabView {
private:
Labels labels{
{{44, 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
{{UI_POS_X_CENTER(19), 5 * 16}, "Not yet implemented", Theme::getInstance()->error_dark->foreground}};
};
class RDSThread {
@@ -174,7 +174,7 @@ class RDSView : public View {
{"Audio", Theme::getInstance()->fg_orange->foreground, &view_audio}};
Labels labels{
{{0 * 8, 28}, "Program type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 28}, "Program type:", Theme::getInstance()->fg_light->foreground},
//{ { 14 * 8, 16 + 8 }, "CC:", Theme::getInstance()->fg_light->foreground },
{{2 * 8, 28 + 16}, "Program ID:", Theme::getInstance()->fg_light->foreground},
//{ { 13 * 8, 32 + 8 }, "Cov:",Theme::getInstance()->fg_light->foreground },
@@ -319,7 +319,7 @@ class RDSView : public View {
"TP"};
TransmitterView tx_view{
16 * 16,
(int16_t)UI_POS_Y_BOTTOM(4),
50000,
9};
+3 -2
View File
@@ -1086,8 +1086,8 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (stepper < 0) stepper++;
if (stepper > 0) stepper--;
} // if( recon || stepper != 0 || index_stepper != 0 )
} // if (frequency_list.size() > 0 )
} /* on_statistics_updates */
} // if (frequency_list.size() > 0 )
} /* on_statistics_updates */
}
handle_retune();
recon_redraw();
@@ -1261,6 +1261,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
else
v = SPEC_MODULATION;
field_mode.set_selected_index(v);
return; // Important ! Guru will occur if you don't return when modifying field from within on_change !
}
last_mode = v;
change_mode(v);
+17 -16
View File
@@ -217,23 +217,24 @@ class ReconView : public View {
std::unique_ptr<RecordView> record_view{};
Labels labels{
{{0 * 8, 0 * 16}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(6), UI_POS_Y(0)}, "VOL: ", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 8 * 16}, "START END", Theme::getInstance()->fg_light->foreground},
{{0 * 8, (22 * 8)}, " S: ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, (24 * 8) + 4}, "NBLCKS:x W,L: , ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, (26 * 8) + 4}, "MODE: , SQUELCH: ", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), (22 * 8)}, " S: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), (24 * 8) + 4}, "NBLCKS:x W,L: , ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), (26 * 8) + 4}, "MODE: , SQUELCH: ", Theme::getInstance()->fg_light->foreground}};
LNAGainField field_lna{
{4 * 8, 0 * 16}};
{4 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{11 * 8, 0 * 16}};
{11 * 8, UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
AudioVolumeField field_volume{
{24 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Text file_name{
// show file used
@@ -297,7 +298,7 @@ class ReconView : public View {
"CONFIG"};
ButtonWithEncoder button_manual_start{
{0 * 8, 9 * 16, 11 * 8, 28},
{UI_POS_X(0), 9 * 16, 11 * 8, 28},
""};
ButtonWithEncoder button_manual_end{
@@ -305,7 +306,7 @@ class ReconView : public View {
""};
OptionsField field_recon_match_mode{
{0 * 8, 11 * 16},
{UI_POS_X(0), 11 * 16},
16, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE
{
{"MATCH:CONTINOUS", 0},
@@ -363,15 +364,15 @@ class ReconView : public View {
};
ButtonWithEncoder button_pause{
{0, (15 * 16) - 4, 72, 28},
{0, (15 * 16) - 4, UI_POS_WIDTH(9), 28},
"PAUSE"};
Button button_audio_app{
{84, (15 * 16) - 4, 72, 28},
{UI_POS_X_CENTER(9), (15 * 16) - 4, UI_POS_WIDTH(9), 28},
"AUDIO"};
ButtonWithEncoder button_add{
{168, (15 * 16) - 4, 72, 28},
{UI_POS_X_RIGHT(9), (15 * 16) - 4, UI_POS_WIDTH(9), 28},
"<STORE>"};
Button button_dir{
@@ -383,15 +384,15 @@ class ReconView : public View {
"RST"};
Button button_mic_app{
{84, (35 * 8) - 4, 72, 28},
{UI_POS_X_CENTER(9), (35 * 8) - 4, UI_POS_WIDTH(9), 28},
"MIC TX"};
ButtonWithEncoder button_remove{
{168, (35 * 8) - 4, 72, 28},
{UI_POS_X_RIGHT(9), (35 * 8) - 4, UI_POS_WIDTH(9), 28},
"<REMOVE>"};
ProgressBar progressbar{
{0 * 8, screen_height / 2 - 16, screen_width, 32}};
{UI_POS_X(0), screen_height / 2 - 16, screen_width, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
@@ -225,7 +225,7 @@ class ReconSetupView : public View {
{"More", Theme::getInstance()->fg_green->foreground, &viewMore}};
Button button_save{
{9 * 8, 255, 14 * 8, 40},
{UI_POS_X_CENTER(18), 255, UI_POS_WIDTH(18), 40},
"SAVE"};
};
+37 -11
View File
@@ -26,17 +26,24 @@
#include "binder.hpp"
#include "string_format.hpp"
#include "ui_freqman.hpp"
#include "audio.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
namespace ui {
void SearchLogger::log_data(SearchRecentEntry& data) {
log_file.write_entry(";" + to_string_short_freq(data.frequency) + ";" + std::to_string(data.duration));
}
template <>
void RecentEntriesTable<SearchRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string str_duration = "";
if (entry.duration < 600)
@@ -44,9 +51,10 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
else
str_duration = to_string_dec_uint(entry.duration / 600) + "m" + to_string_dec_uint((entry.duration / 10) % 60) + "s";
str_duration.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, to_string_short_freq(entry.frequency) + " " + entry.time + " " + str_duration);
str_duration.resize(11, ' ');
std::string freq = to_string_short_freq(entry.frequency);
freq.resize(columns.at(0).second, ' ');
painter.draw_string(target_rect.location(), style, freq + " " + entry.time + " " + str_duration);
}
/* SearchView ********************************************/
@@ -54,6 +62,7 @@ void RecentEntriesTable<SearchRecentEntries>::draw(
SearchView::SearchView(
NavigationView& nav)
: nav_(nav) {
spectrum_row.resize(240);
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
if (!gradient.load_file(default_gradient_file)) {
@@ -75,11 +84,12 @@ SearchView::SearchView(
&check_snap,
&options_snap,
&big_display,
&check_log,
&recent_entries_view});
baseband::set_spectrum(SEARCH_SLICE_WIDTH, 31);
recent_entries_view.set_parent_rect({0, 28 * 8, screen_width, 12 * 8});
recent_entries_view.set_parent_rect({0, 28 * 8, screen_width, screen_height - 28 * 8});
recent_entries_view.on_select = [this, &nav](const SearchRecentEntry& entry) {
nav.push<FrequencySaveView>(entry.frequency);
};
@@ -100,6 +110,14 @@ SearchView::SearchView(
on_range_changed();
});
check_log.on_select = [this](Checkbox&, bool v) {
logging = v;
if (logging) {
logger.append(logs_dir.string() + "/SEARCH_" + to_string_timestamp(rtc_time::now()) + ".CSV");
logger.write_header();
}
};
bind(field_threshold, settings_.power_threshold);
bind(check_snap, settings_.snap_search);
bind(options_snap, settings_.snap_step);
@@ -108,9 +126,15 @@ SearchView::SearchView(
on_range_changed();
receiver_model.enable();
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
audio::output::start();
}
}
SearchView::~SearchView() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
}
@@ -139,9 +163,8 @@ void SearchView::do_detection() {
// Display spectrum
bin_skip_acc = 0;
pixel_index = 0;
display.draw_pixels(
{{0, 88}, {(Dim)spectrum_row.size(), 1}},
spectrum_row);
uint16_t center_align_start = (screen_width - spectrum_row.size()) / 2;
display.draw_pixels({{center_align_start, 88}, {(Dim)spectrum_row.size(), 1}}, spectrum_row);
mean_power = mean_acc / (SEARCH_BIN_NB_NO_DC * slices_nb);
mean_acc = 0;
@@ -192,7 +215,9 @@ void SearchView::do_detection() {
locked = true;
locked_bin = bin_max;
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
// TODO: open Audio.
} else
text_infos.set("Out of range");
@@ -210,6 +235,7 @@ void SearchView::do_detection() {
auto& entry = ::on_packet(recent, resolved_frequency);
entry.set_duration(duration);
if (logging) logger.log_data(entry);
recent_entries_view.set_dirty();
text_infos.set("Listening");
@@ -225,7 +251,7 @@ void SearchView::do_detection() {
// Refresh red tick
portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Theme::getInstance()->fg_red->background);
if (bin_max > -1) {
last_tick_pos = (Coord)(bin_max / slices_nb);
last_tick_pos = (Coord)(bin_max / slices_nb) + center_align_start;
portapack::display.fill_rectangle({last_tick_pos, 90, 1, 6}, Theme::getInstance()->fg_red->foreground);
}
}
@@ -374,7 +400,7 @@ void SearchView::add_spectrum_pixel(Color color) {
bin_skip_acc -= 0x10000;
if (pixel_index < screen_width)
if (pixel_index < spectrum_row.size())
spectrum_row[pixel_index++] = color;
}
+54 -28
View File
@@ -26,6 +26,8 @@
#include "radio_state.hpp"
#include "gradient.hpp"
#include "ui_receiver.hpp"
#include "log_file.hpp"
#include "file_path.hpp"
namespace ui {
@@ -70,6 +72,21 @@ struct SearchRecentEntry {
using SearchRecentEntries = RecentEntries<SearchRecentEntry>;
class SearchLogger {
public:
Optional<File::Error> append(const std::filesystem::path& filename) {
return log_file.append(filename);
}
void log_data(SearchRecentEntry& data);
void write_header() {
log_file.write_raw("Time;Freq;Duration;");
}
private:
LogFile log_file{};
};
class SearchView : public View {
public:
SearchView(NavigationView& nav);
@@ -126,7 +143,7 @@ class SearchView : public View {
uint32_t bin_skip_acc = 0;
uint32_t bin_skip_frac = 0;
uint32_t pixel_index = 0;
std::array<Color, 240> spectrum_row{};
std::vector<Color> spectrum_row{};
ChannelSpectrumFIFO* fifo = nullptr;
uint8_t detect_timer = 0;
@@ -148,6 +165,8 @@ class SearchView : public View {
uint16_t locked_bin = 0;
uint8_t search_counter = 0;
bool locked = false;
bool logging = false;
SearchLogger logger{};
void do_detection();
void do_timers();
@@ -155,75 +174,82 @@ class SearchView : public View {
void on_range_changed();
void add_spectrum_pixel(Color color);
const RecentEntriesColumns columns{{{"Frequency", 9},
{"Time", 8},
{"Duration", 11}}};
RecentEntriesColumns columns{{{"Frequency", 0},
{"Time", 8},
{"Duration", 11}}};
SearchRecentEntries recent{};
RecentEntriesView<RecentEntries<SearchRecentEntry>> recent_entries_view{columns, recent};
Labels labels{
{{1 * 8, 0}, "Min: Max: LNA VGA", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 4 * 8}, "Trig: /255 Mean: /255", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 6 * 8}, "Slices: /32 Rate: Hz", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 10 * 8}, "Timer Status", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 25 * 8}, "Accuracy +/-4.9kHz", Theme::getInstance()->fg_light->foreground},
{{26 * 8, 25 * 8}, "MHz", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(1), UI_POS_Y(0)}, "Min: Max: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(7), UI_POS_Y(0)}, "LNA VGA", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(1), UI_POS_Y(2)}, "Trig: /255", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(12), UI_POS_Y(2)}, "Mean: /255", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(1), UI_POS_Y(3)}, "Slices: /32", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(10), UI_POS_Y(3)}, "Rate: Hz", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(6), UI_POS_Y(5)}, "Timer Status", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(1), 25 * 8}, "Accuracy +/-4.9kHz", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_RIGHT(4), 25 * 8}, "MHz", Theme::getInstance()->fg_light->foreground}};
Checkbox check_log{
{UI_POS_X_RIGHT(6), UI_POS_Y(5)},
3,
"LOG",
true};
FrequencyField field_frequency_min{
{1 * 8, 1 * 16}};
{UI_POS_X(1), UI_POS_Y(1)}};
FrequencyField field_frequency_max{
{11 * 8, 1 * 16}};
{UI_POS_X(11), UI_POS_Y(1)}};
LNAGainField field_lna{
{22 * 8, 1 * 16}};
{UI_POS_X_RIGHT(7), UI_POS_Y(1)}};
VGAGainField field_vga{
{26 * 8, 1 * 16}};
{UI_POS_X_RIGHT(3), UI_POS_Y(1)}};
NumberField field_threshold{
{6 * 8, 2 * 16},
{UI_POS_X(6), UI_POS_Y(2)},
3,
{5, 255},
5,
' '};
Text text_mean{
{22 * 8, 2 * 16, 3 * 8, 16},
{UI_POS_X_RIGHT(7), UI_POS_Y(2), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)},
"---"};
Text text_slices{
{8 * 8, 3 * 16, 2 * 8, 16},
{UI_POS_X(8), UI_POS_Y(3), UI_POS_WIDTH(2), UI_POS_HEIGHT(1)},
"--"};
Text text_rate{
{24 * 8, 3 * 16, 3 * 8, 16},
{UI_POS_X_RIGHT(5), UI_POS_Y(3), UI_POS_WIDTH(3), UI_POS_HEIGHT(1)},
"---"};
VuMeter vu_max{
{1 * 8, 11 * 8 - 4, 3 * 8, 48},
{UI_POS_X(1), 11 * 8 - 4, 3 * 8, 48},
18,
false};
ProgressBar progress_timers{
{6 * 8, 12 * 8, 6 * 8, 16}};
{UI_POS_X(6), UI_POS_Y(6), UI_POS_WIDTH(6), UI_POS_HEIGHT(1)}};
Text text_infos{
{13 * 8, 12 * 8, 15 * 8, 16},
{UI_POS_X(13), UI_POS_Y(6), UI_POS_WIDTH(15), UI_POS_HEIGHT(1)},
"Listening"};
Checkbox check_snap{
{6 * 8, 15 * 8},
{UI_POS_X(6), 15 * 8},
7,
"Snap to:",
true};
OptionsField options_snap{
{17 * 8, 15 * 8}, // Position
7, // Length
{ // Options
{UI_POS_X(17), 15 * 8}, // Position
7, // Length
{ // Options
{"25kHz ", 25'000},
{"12.5kHz", 12'500},
{"8.33kHz", 8'333},
{"2.5kHz", 2'500},
{"500Hz", 500}}};
BigFrequency big_display{
{4, 9 * 16, 28 * 8, 52},
0};
BigFrequency big_display{{UI_POS_X_CENTER(28), UI_POS_Y(9), UI_POS_WIDTH(28), 52}, 0};
MessageHandlerRegistration message_handler_spectrum_config{
Message::ID::ChannelSpectrumConfig,
+201 -9
View File
@@ -6,7 +6,7 @@
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* Copyright (C) 2024 HTotoo
* Copyleft (ɔ) 2024 zxkmm under GPL license
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -33,6 +33,9 @@
#include "ui_touch_calibration.hpp"
#include "ui_text_editor.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "ui_ss_viewer.hpp"
#include "ui_fileman.hpp"
#include "ui_sd_card_debug.hpp"
#include "portapack_persistent_memory.hpp"
#include "lpc43xx_cpp.hpp"
@@ -40,6 +43,7 @@ using namespace lpc43xx;
#include "audio.hpp"
#include "portapack.hpp"
#include "portapack_io.hpp"
using namespace portapack;
#include "file.hpp"
@@ -308,13 +312,45 @@ SetFrequencyCorrectionModel SetRadioView::form_collect() {
};
}
/* SetTXLimitView ************************************/
SetTXLimitView::SetTXLimitView(NavigationView& nav) {
add_children({
&labels,
&tx_gain_max_db,
&tx_disable_switch,
&tx_amp_disable_switch,
&button_save,
&button_cancel,
});
tx_disable_switch.set_value(pmem::config_tx_disabled());
tx_amp_disable_switch.set_value(pmem::config_tx_amp_disabled());
tx_gain_max_db.set_value(pmem::config_tx_gain_max_db());
button_save.on_select = [&nav, this](Button&) {
pmem::set_config_tx_disabled(tx_disable_switch.value());
pmem::set_config_tx_amp_disabled(tx_amp_disable_switch.value());
pmem::set_config_tx_gain_max_db(tx_gain_max_db.value());
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetTXLimitView::focus() {
button_save.focus();
}
/* SetUIView *********************************************/
SetUIView::SetUIView(NavigationView& nav) {
add_children({&checkbox_disable_touchscreen,
&checkbox_bloff,
&options_bloff,
&checkbox_showsplash,
&checkbox_showclock,
&options_clockformat,
&checkbox_guireturnflag,
@@ -341,7 +377,6 @@ SetUIView::SetUIView(NavigationView& nav) {
}
checkbox_disable_touchscreen.set_value(pmem::disable_touchscreen());
checkbox_showsplash.set_value(pmem::config_splash());
checkbox_showclock.set_value(!pmem::hide_clock());
checkbox_guireturnflag.set_value(pmem::show_gui_return_icon());
@@ -380,7 +415,6 @@ SetUIView::SetUIView(NavigationView& nav) {
pmem::set_clock_with_date(false);
}
pmem::set_config_splash(checkbox_showsplash.value());
pmem::set_clock_hidden(!checkbox_showclock.value());
pmem::set_gui_return_icon(checkbox_guireturnflag.value());
pmem::set_disable_touchscreen(checkbox_disable_touchscreen.value());
@@ -413,7 +447,11 @@ void SetUIView::focus() {
/* SetSDCardView *********************************************/
SetSDCardView::SetSDCardView(NavigationView& nav) {
add_children({&labels,
add_children({&status_labels,
&text_card_status,
&text_filesystem_type,
&button_more_info,
&labels,
&checkbox_sdcard_speed,
&button_test_sdcard_high_speed,
&text_sdcard_test_status,
@@ -422,6 +460,10 @@ SetSDCardView::SetSDCardView(NavigationView& nav) {
checkbox_sdcard_speed.set_value(pmem::config_sdcard_high_speed_io());
button_more_info.on_select = [&nav, this](Button&) {
nav.push<SDCardDebugView>();
};
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 !!");
@@ -439,7 +481,79 @@ SetSDCardView::SetSDCardView(NavigationView& nav) {
}
void SetSDCardView::focus() {
button_save.focus();
button_cancel.focus();
}
void SetSDCardView::on_show() {
sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status) {
update_sd_card_status();
};
update_sd_card_status();
}
void SetSDCardView::on_hide() {
sd_card::status_signal -= sd_card_status_signal_token;
}
void SetSDCardView::update_sd_card_status() {
using sd_card::Status;
const auto status = sd_card::status();
// Update card status text
switch (status) {
case Status::NotPresent:
text_card_status.set("Not Inserted");
text_filesystem_type.set("---");
break;
case Status::Present:
text_card_status.set("Inserted");
text_filesystem_type.set("---");
break;
case Status::Mounted:
text_card_status.set("Mounted");
// Determine filesystem type
{
const auto fs_type = sd_card::fs.fs_type;
std::string fs_name;
switch (fs_type) {
case FS_FAT12:
fs_name = "FAT12";
break;
case FS_FAT16:
fs_name = "FAT16";
break;
case FS_FAT32:
fs_name = "FAT32";
break;
case FS_EXFAT:
fs_name = "exFAT";
break;
default:
fs_name = "Unknown";
break;
}
text_filesystem_type.set(fs_name);
}
break;
case Status::ConnectError:
text_card_status.set("Connect Error");
text_filesystem_type.set("---");
break;
case Status::MountError:
text_card_status.set("Mount Error");
text_filesystem_type.set("---");
break;
case Status::IOError:
text_card_status.set("I/O Error");
text_filesystem_type.set("---");
break;
default:
text_card_status.set("Unknown");
text_filesystem_type.set("---");
break;
}
}
/* SetConverterSettingsView ******************************/
@@ -759,7 +873,7 @@ AppSettingsView::AppSettingsView(
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)});
ensure_directory(settings_dir);
@@ -810,9 +924,12 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
&field_fake_brightness,
&button_save,
&button_cancel,
&checkbox_ips_screen_switch,
&checkbox_brightness_switch});
if (portapack::device_type == portapack::DeviceType::DEV_PORTAPACK) {
add_child(&checkbox_ips_screen_switch);
}
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
@@ -1043,7 +1160,10 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
add_children({&labels,
&button_save,
&button_cancel,
&field_battcap,
&button_help_cap,
&checkbox_overridebatt,
&checkbox_battery_replaceable,
&checkbox_battery_charge_hint});
if (i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055)) add_children({&button_reset, &labels2});
@@ -1051,21 +1171,51 @@ SetBatteryView::SetBatteryView(NavigationView& nav) {
button_save.on_select = [&nav, this](Button&) {
pmem::set_ui_override_batt_calc(checkbox_overridebatt.value());
pmem::set_ui_battery_charge_hint(checkbox_battery_charge_hint.value());
pmem::set_battery_replaceable(checkbox_battery_replaceable.value());
battery::BatteryManagement::set_calc_override(checkbox_overridebatt.value());
i2cdev::I2cDev_MAX17055* dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
if (dev) dev->resetChangedFlag();
if ((((uint32_t)field_battcap.value() != pmem::battery_cap_mah()) || (!pmem::battery_cap_valid())) || (dev && dev->getIsBattChanged())) {
pmem::set_battery_cap_mah(field_battcap.value());
if (dev && !dev->reInit()) {
nav.display_modal("Error", "Battery gauge re-init failed");
return;
}
if (dev) {
dev->resetChangedFlag();
dev->update(); // resend the new data
}
}
send_system_refresh();
nav.pop();
};
button_reset.on_select = [&nav, this](Button&) {
auto dev = (i2cdev::I2cDev_MAX17055*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDEVMDL_MAX17055);
if (dev->reset_learned())
if (dev && dev->reset_learned())
nav.display_modal("Reset", "Battery parameters reset");
else
nav.display_modal("Error", "Error parameter reset");
};
button_help_cap.on_select = [&nav, this](Button&) {
nav.display_modal("Battery Capacity",
"Only change default, if you\n"
" changed the battery!\n"
"Defaults:\n"
"H4 + Hackrf One: 2500\n"
"H4 + Hackrf Pro: 2000\n"
"H4Pro + Hackrf Pro: custom\n"
"PortaRf: 3000\n"
);
};
checkbox_overridebatt.set_value(pmem::ui_override_batt_calc());
checkbox_battery_charge_hint.set_value(pmem::ui_battery_charge_hint());
checkbox_battery_replaceable.set_value(pmem::battery_replaceable());
field_battcap.set_value(pmem::battery_cap_mah());
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
@@ -1076,6 +1226,46 @@ void SetBatteryView::focus() {
button_cancel.focus();
}
/* SetSlpash *********************************************/
SetSplash::SetSplash(NavigationView& nav) {
add_children({&checkbox_showsplash,
&checkbox_randomsplash,
&message,
&button_picture_select,
&button_save,
&button_cancel});
checkbox_showsplash.set_value(pmem::config_splash());
splash_bmp_exists = file_exists(splash_dot_bmp);
checkbox_randomsplash.set_value(!splash_bmp_exists);
message.hidden(splash_bmp_exists);
checkbox_randomsplash.on_select = [this](Checkbox&, bool v) {
random_enabled = v;
};
button_picture_select.on_select = [this, &nav](Button&) {
auto ret = nav.push<FileManagerView>();
ret->push_dir(splash_dir);
};
button_save.on_select = [&nav, this](Button&) {
if (random_enabled == true) delete_file(splash_dot_bmp);
pmem::set_config_splash(checkbox_showsplash.value());
send_system_refresh();
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetSplash::focus() {
button_save.focus();
}
/* SettingsMenuView **************************************/
SettingsMenuView::SettingsMenuView(NavigationView& nav)
@@ -1103,11 +1293,13 @@ void SettingsMenuView::on_populate() {
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<SetPersistentMemoryView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetRadioView>(); }},
{"TX Limit", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetTXLimitView>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SetSDCardView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [this]() { nav_.push<SetUIView>(); }},
{"Display", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetDisplayView>(); }},
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
{"Theme", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetThemeView>(); }},
{"Splash settings", ui::Color::dark_cyan(), &bitmap_icon_file_image, [this]() { nav_.push<SetSplash>(); }},
});
if (battery::BatteryManagement::isDetected()) add_item({"Battery", ui::Color::dark_cyan(), &bitmap_icon_batt_icon, [this]() { nav_.push<SetBatteryView>(); }});
+209 -86
View File
@@ -5,7 +5,7 @@
* Copyright (C) 2023 Kyle Reed
* Copyright (C) 2024 Mark Thompson
* Copyright (C) 2024 u-foka
* Copyleft (ɔ) 2024 zxkmm under GPL license
* copyleft 2024 zxkmm
*
* This file is part of PortaPack.
*
@@ -34,6 +34,7 @@
#include "ui_navigation.hpp"
#include "bitmap.hpp"
#include "ff.h"
#include "sd_card.hpp"
#include "portapack_persistent_memory.hpp"
#include "irq_controls.hpp"
@@ -72,8 +73,8 @@ class SetDateTimeView : public View {
{{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Theme::getInstance()->fg_medium->foreground},
{{5 * 8, 6 * 16}, "- - : :", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 12 * 16}, "Start: 0:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 13 * 16}, "End: 1:00 on Nth DDD in", Theme::getInstance()->fg_light->foreground}};
NumberField field_year{
{1 * 8, 6 * 16},
@@ -170,10 +171,10 @@ class SetDateTimeView : public View {
{}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel"};
void form_init(const SetDateTimeModel& model);
@@ -253,10 +254,10 @@ class SetRadioView : public View {
"Disable external TCXO"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
@@ -264,6 +265,51 @@ class SetRadioView : public View {
SetFrequencyCorrectionModel form_collect();
};
class SetTXLimitView : public View {
public:
SetTXLimitView(NavigationView& nav);
void focus() override;
std::string title() const override { return "TX Limit"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Limits RF TX Gain", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "(This may affect", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 3 * 16}, "all applications.)", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 12 * 16}, "TX Max Gain:", Theme::getInstance()->fg_light->foreground},
};
Checkbox tx_disable_switch{
{1 * 8, 6 * 16},
23,
"Disable TX"};
Checkbox tx_amp_disable_switch{
{1 * 8, 8 * 16},
23,
"Disable TX Amp"};
NumberField tx_gain_max_db{
{20 * 8, 12 * 16},
6,
{0, 47},
1,
' ',
};
Button button_save{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
using portapack::persistent_memory::backlight_timeout_t;
class SetUIView : public View {
@@ -298,85 +344,80 @@ class SetUIView : public View {
{"1 hour", backlight_timeout_t::Timeout3600Sec},
}};
Checkbox checkbox_showsplash{
{3 * 8, 6 * 16},
20,
"Show splash"};
Checkbox checkbox_showclock{
{3 * 8, 8 * 16},
{3 * 8, 6 * 16},
20,
"Show clock with:"};
OptionsField options_clockformat{
{60, 9 * 16 + 8},
{60, 7 * 16 + 8},
20,
{{"time only", 0},
{"time and date", 1}}};
Checkbox checkbox_guireturnflag{
{3 * 8, 11 * 16},
{3 * 8, 9 * 16},
20,
"Back button in menu"};
Labels labels{
{{3 * 8, 13 * 16}, "Show/Hide Status Icons", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 11 * 16}, "Show/Hide Status Icons", Theme::getInstance()->fg_light->foreground},
};
ImageToggle toggle_camera{
{2 * 8, 14 * 16 + 2, 16, 16},
{2 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_camera};
ImageToggle toggle_sleep{
{4 * 8, 14 * 16 + 2, 16, 16},
{4 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_sleep};
ImageToggle toggle_stealth{
{6 * 8, 14 * 16 + 2, 16, 16},
{6 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_stealth};
ImageToggle toggle_converter{
{8 * 8, 14 * 16 + 2, 16, 16},
{8 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_upconvert};
ImageToggle toggle_bias_tee{
{10 * 8, 14 * 16 + 2, 16, 16},
{10 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_biast_off};
ImageToggle toggle_clock{
{12 * 8, 14 * 16 + 2, 8, 16},
{12 * 8, 12 * 16 + 2, 8, 16},
&bitmap_icon_clk_ext};
ImageToggle toggle_mute{
{13 * 8, 14 * 16 + 2, 16, 16},
{13 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_speaker_and_headphones_mute};
ImageToggle toggle_speaker{
{15 * 8, 14 * 16 + 2, 16, 16},
{15 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_speaker_mute};
ImageToggle toggle_battery_icon{
{17 * 8, 14 * 16 + 2, 16, 16},
{17 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_batt_icon};
ImageToggle toggle_battery_text{
{19 * 8, 14 * 16 + 2, 16, 16},
{19 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_batt_text};
ImageToggle toggle_fake_brightness{
{21 * 8, 14 * 16 + 2, 16, 16},
{21 * 8, 12 * 16 + 2, 16, 16},
&bitmap_icon_brightness};
ImageToggle toggle_sd_card{
{23 * 8, 14 * 16 + 2, 16, 16},
{23 * 8, 12 * 16 + 2, 16, 16},
&bitmap_sd_card_ok};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel"};
};
@@ -384,36 +425,60 @@ class SetSDCardView : public View {
public:
SetSDCardView(NavigationView& nav);
void on_show() override;
void on_hide() override;
void focus() override;
std::string title() const override { return "SD Card"; };
private:
SignalToken sd_card_status_signal_token{};
// Status section (top half)
Labels status_labels{
{{10, 24}, "Card Status:", Theme::getInstance()->fg_light->foreground},
{{10, 48}, "Filesystem:", Theme::getInstance()->fg_light->foreground}};
Text text_card_status{
{120, 24, 110, 16},
""};
Text text_filesystem_type{
{120, 48, 110, 16},
""};
Button button_more_info{
{UI_POS_X_CENTER(20), 90, UI_POS_WIDTH(20), UI_POS_HEIGHT(2)},
"More Info"};
// Settings section (bottom half)
Labels labels{
// 01234567890123456789012345678
{{1 * 8, 120 - 48}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 120 - 32}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X_CENTER(26), 140}, " HIGH SPEED SDCARD IO ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(26), 156}, " May or may not work !! ", Theme::getInstance()->fg_light->foreground}};
Checkbox checkbox_sdcard_speed{
{2 * 8, 120},
{UI_POS_X_CENTER(26), 180},
20,
"enable high speed IO"};
Button button_test_sdcard_high_speed{
{2 * 8, 152, 27 * 8, 32},
{UI_POS_X_CENTER(27), 210, UI_POS_WIDTH(27), UI_POS_HEIGHT(2)},
"TEST BUTTON (NO PMEM SAVE)"};
Text text_sdcard_test_status{
{2 * 8, 198, 28 * 8, 16},
{UI_POS_X_CENTER(28), 256, UI_POS_WIDTH(28), UI_POS_HEIGHT(1)},
""};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel"};
void update_sd_card_status();
};
class SetConverterSettingsView : public View {
@@ -455,7 +520,7 @@ class SetConverterSettingsView : public View {
{8 * 8, 10 * 16}};
Button button_return{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Return",
};
};
@@ -498,7 +563,7 @@ class SetFrequencyCorrectionView : public View {
{8 * 8, 10 * 16}};
Button button_return{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, UI_POS_HEIGHT(2)},
"Return",
};
};
@@ -536,11 +601,11 @@ class SetAudioView : public View {
"Beep on RX packets"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -559,11 +624,11 @@ class SetEncoderDialView : public View {
private:
Labels labels{
{{0 * 8, 0 * 16}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Sensitivity to dial rotation", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 1 * 16}, "position (x steps per 360):", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 3 * 16}, "Sensitivity:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "Rotation rate (default 1", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 8 * 16}, "means no rate dependency):", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "Rate multiplier:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 14 * 16}, "Direction:", Theme::getInstance()->fg_light->foreground},
@@ -606,11 +671,11 @@ class SetEncoderDialView : public View {
"-"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -649,11 +714,11 @@ class SetButtonsView : public View {
{"FAST", true}}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -674,7 +739,7 @@ class SetPersistentMemoryView : public View {
};
Text text_pmem_status{
{1 * 8, 4 * 16 + 8, 28 * 8, 16},
{UI_POS_X_CENTER(28), 4 * 16 + 8, 28 * 8, 16},
""};
Checkbox check_use_sdcard_for_pmem{
@@ -683,19 +748,19 @@ class SetPersistentMemoryView : public View {
"Use SD card for P.Mem"};
Button button_save_mem_to_file{
{1 * 8, 8 * 16, 28 * 8, 2 * 16},
{UI_POS_X_CENTER(28), 8 * 16, 28 * 8, 2 * 16},
"Save P.Mem to SD card"};
Button button_load_mem_from_file{
{1 * 8, 10 * 16 + 2, 28 * 8, 2 * 16},
{UI_POS_X_CENTER(28), 10 * 16 + 2, 28 * 8, 2 * 16},
"Load P.Mem from SD Card"};
Button button_load_mem_defaults{
{1 * 8, 12 * 16 + 4, 28 * 8, 2 * 16},
{UI_POS_X_CENTER(28), 12 * 16 + 4, 28 * 8, 2 * 16},
"Reset P.Mem to defaults"};
Button button_return{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Return",
};
};
@@ -713,7 +778,7 @@ class AppSettingsView : public View {
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, screen_width, 26 * 8},
{0, 2 * 8, screen_width, UI_POS_HEIGHT_REMAINING(3)},
true};
};
@@ -738,11 +803,11 @@ class SetConfigModeView : public View {
"Config Mode enable"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), 12 * 8, 32},
"Cancel",
};
};
@@ -784,11 +849,11 @@ class SetDisplayView : public View {
"IPS Screen"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
@@ -841,19 +906,19 @@ class SetTouchscreenThresholdView : public View {
};
Button button_autodetect{
{2 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 13 * 16, 12 * 8, 32},
"Auto Detect"};
Button button_reset{
{16 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 13 * 16, 12 * 8, 32},
"Reset",
};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), 16 * 16, 12 * 8, 32},
"Cancel",
};
@@ -876,14 +941,14 @@ class SetMenuColorView : public View {
void paint_sample();
Labels labels{
{{3 * 8, 1 * 16}, "Menu Button Color Scheme", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(25), 1 * 16}, "Menu Button Color Scheme", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 8 * 16}, "Red Level:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 9 * 16}, "Green Level:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "Blue Level:", Theme::getInstance()->fg_light->foreground},
};
NewButton button_sample{
{8 * 8, 4 * 16, 14 * 8, 3 * 16},
{UI_POS_X_CENTER(14), 4 * 16, 14 * 8, 3 * 16},
"New Color",
&bitmap_icon_brightness,
};
@@ -913,16 +978,16 @@ class SetMenuColorView : public View {
};
Button button_reset{
{2 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(7), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Reset",
};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
@@ -941,12 +1006,8 @@ class SetThemeView : public View {
{{1 * 8, 1 * 16}, "Select a theme.", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "Restart PP to fully apply!", Theme::getInstance()->fg_light->foreground}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
OptionsField options{
{0 * 8, 4 * 16},
{UI_POS_X(0), 4 * 16},
(size_t)(screen_width / 8),
{
{"Default - Grey", 0},
@@ -954,6 +1015,7 @@ class SetThemeView : public View {
{"Aqua", 2},
{"Green", 3},
{"Red", 4},
{"Dark", 5},
},
true};
@@ -962,8 +1024,12 @@ class SetThemeView : public View {
23,
"Set Menu color too"};
Button button_save{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
};
@@ -979,35 +1045,55 @@ class SetBatteryView : public View {
private:
int32_t selected = 0;
Labels labels{
{{1 * 8, 1 * 16}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "method to voltage based", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(26), UI_POS_Y(0)}, "Override batt calculation", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(24), UI_POS_Y(1)}, "method to voltage based", Theme::getInstance()->fg_light->foreground},
/**/
{{1 * 8, 6 * 16}, "Display a hint to remind you", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 7 * 16}, "when you charge", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X_CENTER(29), UI_POS_Y(4)}, "Display a hint to remind you", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(16), UI_POS_Y(5)}, "when you charge", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X_CENTER(17), UI_POS_Y(8)}, "Battery capacity", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(7), UI_POS_Y(9)}, "mAh", Theme::getInstance()->fg_light->foreground}};
Labels labels2{{{1 * 8, 11 * 16}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
Labels labels2{{{UI_POS_X(1), UI_POS_Y_BOTTOM(7)}, "Reset IC's learned params.", Theme::getInstance()->fg_light->foreground}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
NumberField field_battcap{
{UI_POS_X(1), UI_POS_Y(9)},
5,
{BATT_18650_MIN_MAH, BATT_18650_MAX_MAH},
100,
' ',
};
Button button_help_cap{
{UI_POS_X(12), UI_POS_Y(9), UI_POS_WIDTH(5), UI_POS_HEIGHT(1)},
"Help",
};
Checkbox checkbox_overridebatt{
{2 * 8, 4 * 16},
{UI_POS_X(2), UI_POS_Y(2)},
23,
"Override"};
Checkbox checkbox_battery_charge_hint{
{2 * 8, 9 * 16},
{UI_POS_X(2), UI_POS_Y(6)},
23,
"Charge hint"};
Checkbox checkbox_battery_replaceable{
{UI_POS_X(2), UI_POS_Y(11)},
23,
"Replaceable battery"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel",
};
Button button_save{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_reset{
{2 * 8, 13 * 16, 12 * 8, 32},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(6), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Reset",
};
};
@@ -1023,6 +1109,43 @@ class SettingsMenuView : public BtnGridView {
void on_populate() override;
};
class SetSplash : public View {
public:
SetSplash(NavigationView& nav);
void focus() override;
std::string title() const override { return "Splash"; };
private:
bool splash_bmp_exists = false;
bool random_enabled = false;
Checkbox checkbox_showsplash{
{3 * 8, 1 * 16},
20,
"Show splash"};
Checkbox checkbox_randomsplash{
{3 * 8, 3 * 16},
20,
"Random splash on boot"};
Text message{{UI_POS_X(1), UI_POS_Y(6), UI_POS_WIDTH(26), UI_POS_HEIGHT(1)}, "Pick image to stop random."};
Button button_picture_select{
{UI_POS_X_CENTER(8) - UI_POS_WIDTH(4), UI_POS_Y_BOTTOM(7), UI_POS_WIDTH(13), UI_POS_HEIGHT(2)},
"Select pict."};
Button button_save{
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Save"};
Button button_cancel{
{UI_POS_X_CENTER(16) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(2)},
"Cancel"};
};
} /* namespace ui */
#endif /*__UI_SETTINGS_H__*/
+35 -12
View File
@@ -30,11 +30,10 @@
#include "portapack.hpp"
#include <cstring>
#include <stdio.h>
#include "rtc_time.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
#include "string_format.hpp"
#include "complex.hpp"
void SondeLogger::on_packet(const sonde::Packet& packet) {
@@ -54,6 +53,7 @@ SondeView::SondeView(NavigationView& nav)
&field_lna,
&field_vga,
&rssi,
&channel,
&field_volume,
&check_log,
&check_crc,
@@ -64,6 +64,8 @@ SondeView::SondeView(NavigationView& nav)
&text_frame,
&text_temp,
&text_humid,
&text_press,
&text_vspeed,
&geopos,
&button_see_qr,
&button_see_map});
@@ -106,7 +108,7 @@ SondeView::SondeView(NavigationView& nav)
logger = std::make_unique<SondeLogger>();
if (logger)
logger->append(logs_dir / u"SONDE.TXT");
logger->append(logs_dir / u"SONDE_" + to_string_timestamp(rtc_time::now()) + u".TXT");
if (pmem::beep_on_packets()) {
audio::set_rate(audio::Rate::Hz_24000);
@@ -154,7 +156,7 @@ void SondeView::on_packet(const sonde::Packet& packet) {
sonde_id = packet.serial_number(); // used also as tag on the geomap
text_serial.set(sonde_id);
text_timestamp.set(to_string_timestamp(packet.received_at()));
text_timestamp.set(to_string_datetime(packet.received_at(), TimeFormat::YMDHMS));
text_voltage.set(unit_auto_scale(packet.battery_voltage(), 2, 2) + "V");
@@ -162,21 +164,42 @@ void SondeView::on_packet(const sonde::Packet& packet) {
temp_humid_info = packet.get_temp_humid();
if (temp_humid_info.humid != 0) {
double decimals = abs(get_decimals(temp_humid_info.humid, 10, true));
text_humid.set(to_string_dec_int((int)temp_humid_info.humid) + "." + to_string_dec_uint(decimals, 1) + "%");
text_humid.set(to_string_decimal(temp_humid_info.humid, 1) + "%");
}
if (temp_humid_info.temp != 0) {
double decimals = abs(get_decimals(temp_humid_info.temp, 10, true));
text_temp.set(to_string_dec_int((int)temp_humid_info.temp) + "." + to_string_dec_uint(decimals, 1) + STR_DEGREES_C);
text_temp.set(to_string_decimal(temp_humid_info.temp, 1) + STR_DEGREES_C);
}
if (packet.get_pressure() != 0) {
text_press.set(to_string_decimal(packet.get_pressure(), 1) + " hPa");
}
gps_info = packet.get_GPS_data();
geopos.set_altitude(gps_info.alt);
geopos.set_lat(gps_info.lat);
geopos.set_lon(gps_info.lon);
if (last_timestamp_update_ != 0 && last_altitude_ != 0) {
// calculate speeds
float vspeed = 0;
time_t currpackettime = rtc_time::rtcToUnixUTC(packet.received_at());
int32_t time_diff = (currpackettime - last_timestamp_update_);
if (time_diff >= 10) { // update only every 10 seconds
vspeed = (static_cast<int>(gps_info.alt) - static_cast<int>(last_altitude_)) / (float)time_diff;
last_timestamp_update_ = currpackettime;
last_altitude_ = gps_info.alt;
text_vspeed.set(to_string_decimal(vspeed, 1) + " m/s");
}
} else { // save first valid packet time + altitude
last_timestamp_update_ = rtc_time::rtcToUnixUTC(packet.received_at());
last_altitude_ = geopos.altitude();
}
if (gps_info.is_valid()) { // only update when valid, to prevent flashing
geopos.set_altitude(gps_info.alt);
geopos.set_lat(gps_info.lat);
geopos.set_lon(gps_info.lon);
if (geomap_view_) {
geomap_view_->update_position(gps_info.lat, gps_info.lon, 400, gps_info.alt, 0);
}
}
if (logger && logging) {
logger->on_packet(packet);
}
+41 -26
View File
@@ -30,6 +30,7 @@
#include "ui_rssi.hpp"
#include "ui_qrcode.hpp"
#include "ui_geomap.hpp"
#include "string_format.hpp"
#include "event_m0.hpp"
@@ -94,86 +95,100 @@ class SondeView : public View {
// AudioOutput audio_output { };
Labels labels{
{{4 * 8, 2 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 3 * 16}, "ID:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "DateTime:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(4), UI_POS_Y(2)}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(6), UI_POS_Y(3)}, "ID:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(4)}, "DateTime:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 5 * 16}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 6 * 16}, "Frame:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 7 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 8 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(3), UI_POS_Y(5)}, "Vbatt:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(3), UI_POS_Y(6)}, "Frame:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(4), UI_POS_Y(7)}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(8)}, "Humidity:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(9)}, "Pressure:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(2), UI_POS_Y(10)}, "VSpeed:", Theme::getInstance()->fg_light->foreground}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{UI_POS_X(21), UI_POS_Y(0) + 5, UI_POS_WIDTH_REMAINING(24), 4},
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
Checkbox check_log{
{22 * 8, 8 * 16},
{UI_POS_X_RIGHT(8), UI_POS_Y(8)},
3,
"Log"};
Checkbox check_crc{
{22 * 8, 10 * 16},
{UI_POS_X_RIGHT(8), UI_POS_Y(10)},
3,
"CRC"};
Text text_signature{
{9 * 8, 2 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(2), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
"..."};
Text text_serial{
{9 * 8, 3 * 16, 11 * 8, 16},
{UI_POS_X(9), UI_POS_Y(3), UI_POS_WIDTH_REMAINING(10), UI_POS_HEIGHT(1)},
"..."};
Text text_timestamp{
{9 * 8, 4 * 16, 11 * 8, 16},
{UI_POS_X(9), UI_POS_Y(4), UI_POS_WIDTH_REMAINING(9), UI_POS_HEIGHT(1)},
"..."};
Text text_voltage{
{9 * 8, 5 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(5), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_frame{
{9 * 8, 6 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(6), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_temp{
{9 * 8, 7 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(7), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_humid{
{9 * 8, 8 * 16, 10 * 8, 16},
{UI_POS_X(9), UI_POS_Y(8), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_press{
{UI_POS_X(9), UI_POS_Y(9), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
Text text_vspeed{
{UI_POS_X(9), UI_POS_Y(10), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)},
"..."};
GeoPos geopos{
{0, 12 * 16},
{UI_POS_X(0), UI_POS_Y(12)},
GeoPos::alt_unit::METERS,
GeoPos::spd_unit::HIDDEN};
Button button_see_qr{
{2 * 8, 15 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) - UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See QR"};
Button button_see_map{
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
{UI_POS_X_CENTER(12) + UI_POS_WIDTH(8), UI_POS_Y_BOTTOM(4), UI_POS_WIDTH(12), UI_POS_HEIGHT(3)},
"See on map"};
GeoMapView* geomap_view_{nullptr};
time_t last_timestamp_update_{0};
uint32_t last_altitude_{0};
MessageHandlerRegistration message_handler_packet{
Message::ID::SondePacket,
@@ -27,8 +27,6 @@ namespace fs = std::filesystem;
namespace ui {
const std::filesystem::path splash_dot_bmp{u"/splash.bmp"};
ScreenshotViewer::ScreenshotViewer(
NavigationView& nav,
const std::filesystem::path& path)
@@ -44,7 +42,6 @@ bool ScreenshotViewer::on_key(KeyEvent) {
void ScreenshotViewer::paint(Painter& painter) {
File file{};
painter.fill_rectangle({0, 0, screen_width, screen_height}, Color::black());
auto show_invalid = [&]() {
+1 -3
View File
@@ -28,11 +28,10 @@
#include "ui_painter.hpp"
#include "ui_widget.hpp"
#include "file.hpp"
#include "file_path.hpp"
namespace ui {
extern const std::filesystem::path splash_dot_bmp;
class ScreenshotViewer : public View {
public:
ScreenshotViewer(NavigationView& nav, const std::filesystem::path& path);
@@ -49,7 +48,6 @@ class SplashViewer : public View {
SplashViewer(NavigationView& nav, const std::filesystem::path& path);
bool on_key(KeyEvent key) override;
void paint(Painter& painter) override;
void update_ss(void);
private:
NavigationView& nav_;
+144 -26
View File
@@ -27,11 +27,14 @@
#include "ui_font_fixed_5x8.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "portapack.hpp"
#include "file.hpp"
namespace ui {
void create_thread(int32_t (*fn)(void*), void* arg, size_t stack_size, int priority) {
// TODO: collect memory on terminate, once this is used
// TODO: collect memory on terminate, once this is used. This is a HUGE TODO! need to call chThdWait on all of them!
chThdCreateFromHeap(NULL, stack_size, priority, fn, arg);
}
@@ -93,34 +96,146 @@ bool i2c_read(uint8_t* cmd, size_t cmd_len, uint8_t* data, size_t data_len) {
return dev->i2c_read(cmd, cmd_len, data, data_len);
}
// v3
// Version 3
FRESULT ext_f_open(FIL* fp, const TCHAR* path, BYTE mode) {
return f_open(fp, path, mode);
}
FRESULT ext_f_close(FIL* fp) {
return f_close(fp);
}
FRESULT ext_f_read(FIL* fp, void* buff, UINT btr, UINT* br) {
return f_read(fp, buff, btr, br);
}
FRESULT ext_f_write(FIL* fp, const void* buff, UINT btw, UINT* bw) {
return f_write(fp, buff, btw, bw);
}
FRESULT ext_f_lseek(FIL* fp, FSIZE_t ofs) {
return f_lseek(fp, ofs);
}
FRESULT ext_f_truncate(FIL* fp) {
return f_truncate(fp);
}
FRESULT ext_f_sync(FIL* fp) {
return f_sync(fp);
}
FRESULT ext_f_opendir(DIR* dp, const TCHAR* path) {
return f_opendir(dp, path);
}
FRESULT ext_f_closedir(DIR* dp) {
return f_closedir(dp);
}
FRESULT ext_f_readdir(DIR* dp, FILINFO* fno) {
return f_readdir(dp, fno);
}
FRESULT ext_f_findfirst(DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern) {
return f_findfirst(dp, fno, path, pattern);
}
FRESULT ext_f_findnext(DIR* dp, FILINFO* fno) {
return f_findnext(dp, fno);
}
FRESULT ext_f_mkdir(const TCHAR* path) {
return f_mkdir(path);
}
FRESULT ext_f_unlink(const TCHAR* path) {
return f_unlink(path);
}
FRESULT ext_f_rename(const TCHAR* path_old, const TCHAR* path_new) {
return f_rename(path_old, path_new);
}
FRESULT ext_f_stat(const TCHAR* path, FILINFO* fno) {
return f_stat(path, fno);
}
FRESULT ext_f_utime(const TCHAR* path, const FILINFO* fno) {
return f_utime(path, fno);
}
FRESULT ext_f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs) {
return f_getfree(path, nclst, fatfs);
}
FRESULT ext_f_mount(FATFS* fs, const TCHAR* path, BYTE opt) {
return f_mount(fs, path, opt);
}
int ext_f_putc(TCHAR c, FIL* fp) {
return f_putc(c, fp);
}
int ext_f_puts(const TCHAR* str, FIL* cp) {
return f_puts(str, cp);
}
int ext_f_printf(FIL* fp, const TCHAR* str, ...) {
return f_printf(fp, str);
}
TCHAR* ext_f_gets(TCHAR* buff, int len, FIL* fp) {
return f_gets(buff, len, fp);
}
void ext_draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count) {
portapack::display.draw_pixels(r, colors, count);
}
void ext_draw_pixel(const ui::Point p, const ui::Color color) {
portapack::display.draw_pixel(p, color);
}
StandaloneView* standaloneView = nullptr;
void exit_app() {
if (standaloneView) standaloneView->exit();
}
void set_dirty() {
if (standaloneView != nullptr)
standaloneView->set_dirty();
}
standalone_application_api_t api = {
/* .malloc = */ &alloc,
/* .calloc = */ &calloc,
/* .realloc = */ &realloc,
/* .free = */ &chHeapFree,
/* .create_thread = */ &create_thread,
/* .fill_rectangle = */ &fill_rectangle,
/* .swizzled_switches = */ &swizzled_switches,
/* .get_switches_state = */ &get_switches_state_ulong,
/* .fixed_5x8_glyph_data = */ ui::font::fixed_5x8.get_data(),
/* .fixed_8x16_glyph_data = */ ui::font::fixed_8x16.get_data(),
/* .fill_rectangle_unrolled8 = */ &fill_rectangle_unrolled8,
/* .draw_bitmap = */ &draw_bitmap,
/* .scroll_area_y = */ &scroll_area_y,
/* .scroll_set_area = */ &scroll_set_area,
/* .scroll_disable = */ &scroll_disable,
/* .scroll_set_position = */ &scroll_set_position,
/* .scroll = */ &scroll,
/* .i2c_read = */ &i2c_read,
/* .panic = */ &chDbgPanic,
/* .set_dirty = */ &set_dirty,
.malloc = &alloc,
.calloc = &calloc,
.realloc = &realloc,
.free = &chHeapFree,
.create_thread = &create_thread,
.fill_rectangle = &fill_rectangle,
.swizzled_switches = &swizzled_switches,
.get_switches_state = &get_switches_state_ulong,
.fixed_5x8_glyph_data = ui::font::fixed_5x8.get_data(),
.fixed_8x16_glyph_data = ui::font::fixed_8x16.get_data(),
.fill_rectangle_unrolled8 = &fill_rectangle_unrolled8,
.draw_bitmap = &draw_bitmap,
.scroll_area_y = &scroll_area_y,
.scroll_set_area = &scroll_set_area,
.scroll_disable = &scroll_disable,
.scroll_set_position = &scroll_set_position,
.scroll = &scroll,
.i2c_read = &i2c_read,
.panic = &chDbgPanic,
.set_dirty = &set_dirty,
// Version 3
.f_open = &ext_f_open,
.f_close = &ext_f_close,
.f_read = &ext_f_read,
.f_write = &ext_f_write,
.f_lseek = &ext_f_lseek,
.f_truncate = &ext_f_truncate,
.f_sync = &ext_f_sync,
.f_opendir = &ext_f_opendir,
.f_closedir = &ext_f_closedir,
.f_readdir = &ext_f_readdir,
.f_findfirst = &ext_f_findfirst,
.f_findnext = &ext_f_findnext,
.f_mkdir = &ext_f_mkdir,
.f_unlink = &ext_f_unlink,
.f_rename = &ext_f_rename,
.f_stat = &ext_f_stat,
.f_utime = &ext_f_utime,
.f_getfree = &ext_f_getfree,
.f_mount = &ext_f_mount,
.f_putc = &ext_f_putc,
.f_puts = &ext_f_puts,
.f_printf = &ext_f_printf,
.f_gets = &ext_f_gets,
.draw_pixels = &ext_draw_pixels,
.draw_pixel = &ext_draw_pixel,
.exit_app = &exit_app,
// version 4
.screen_height = &screen_height,
.screen_width = &screen_width,
};
StandaloneView::StandaloneView(NavigationView& nav, uint8_t* app_image)
@@ -158,22 +273,21 @@ bool StandaloneView::on_key(const KeyEvent key) {
if (get_application_information()->header_version > 1) {
return get_application_information()->OnKeyEvent((uint8_t)key);
}
return false;
return true;
}
bool StandaloneView::on_encoder(const EncoderEvent event) {
if (get_application_information()->header_version > 1) {
return get_application_information()->OnEncoder((int32_t)event);
}
return false;
return true;
}
bool StandaloneView::on_touch(const TouchEvent event) {
if (get_application_information()->header_version > 1) {
get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
return get_application_information()->OnTouchEvent(event.point.x(), event.point.y(), (uint32_t)event.type);
}
return true;
return false;
}
bool StandaloneView::on_keyboard(const KeyboardEvent event) {
@@ -202,4 +316,8 @@ void StandaloneView::on_before_detach() {
context().focus_manager().clearMirror();
}
void StandaloneView::exit() {
nav_.pop();
}
} // namespace ui
@@ -51,6 +51,8 @@ class StandaloneView : public View {
void frame_sync();
void exit();
private:
bool initialized = false;
NavigationView& nav_;
+152 -8
View File
@@ -25,6 +25,10 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "file_path.hpp"
#include "ui_textentry.hpp"
#include "../keeloq_keystore.hpp"
#include "../keeloq_file.hpp"
#include "../keeloq_common.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
@@ -56,6 +60,51 @@ void SubGhzDRecentEntryDetailView::update_data() {
if (cnt != SD_NO_CNT) console.writeln("Cnt: " + to_string_dec_uint(cnt));
if (entry_.data != 0) console.writeln("Data: " + to_string_hex(entry_.data));
if (entry_.sensorType == FPS_RESTAURANT_PAGER) {
uint8_t pager_addr = (entry_.data >> 5) & 0x0F;
uint8_t func_code = (entry_.data >> 1) & 0x0F;
console.writeln("Station: 0x" + to_string_hex(serial) + " (" + to_string_dec_uint(serial) + ")");
console.writeln("Pager: " + to_string_dec_uint(pager_addr));
if (func_code == 0x0D)
console.writeln("Action: Buzz");
else if (func_code == 0x0F)
console.writeln("Action: Sync");
else
console.writeln("Action: 0x" + to_string_hex(func_code));
}
if (entry_.sensorType == FPS_KEELOQ) {
console.writeln("Fix: " + to_string_hex(fix));
console.writeln("Encrypted: " + to_string_hex(encrypted));
console.writeln("Manufacturer: " + mf_name);
if (hop != SD_NO_HOP) {
console.writeln("Hop: " + to_string_hex(hop));
add_children({&button_save});
button_save.on_select = [this](const Button&) {
keeloq_file_buffer.clear();
text_prompt(
nav_,
keeloq_file_buffer,
64,
ENTER_KEYBOARD_MODE_ALPHA,
[this](std::string& buffer) {
KeeloqData params{
mf_name,
serial,
(uint16_t)cnt,
btn};
ensure_directory(keeloq_remotes_dir);
write_keeloq_file(keeloq_remotes_dir / buffer + ".KEELOQ", params);
});
};
}
}
}
SubGhzDRecentEntryDetailView::SubGhzDRecentEntryDetailView(NavigationView& nav, const SubGhzDRecentEntry& entry)
@@ -84,6 +133,7 @@ void SubGhzDView::focus() {
SubGhzDView::SubGhzDView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
@@ -241,6 +291,10 @@ const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) {
return "GangQi";
case FPS_MARANTEC24:
return "Marantec24";
case FPS_HOLTEKHT6P20B:
return "Holtek HT6P20B";
case FPS_RESTAURANT_PAGER:
return "Rest. Pager";
case FPS_Invalid:
default:
return "Unknown";
@@ -261,17 +315,21 @@ void RecentEntriesTable<ui::SubGhzDRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string line{};
line.reserve(30);
line = SubGhzDView::getSensorTypeName((FPROTO_SUBGHZD_SENSOR)entry.sensorType);
line = line + " " + to_string_hex(entry.data << 32);
if (line.length() < 19) {
line += SubGhzDView::pad_string_with_spaces(19 - line.length());
if (entry.sensorType == FPS_RESTAURANT_PAGER) {
uint8_t pgr = (entry.data >> 5) & 0x0F;
uint8_t func = (entry.data >> 1) & 0x0F;
line += " P:" + to_string_dec_uint(pgr) + (func == 0x0D ? " Buzz" : func == 0x0F ? " Sync"
: "");
} else {
line = truncate(line, 19);
line = line + " " + to_string_hex(entry.data << 32);
}
line.resize(columns.at(0).second, ' ');
std::string ageStr = to_string_dec_uint(entry.age);
std::string bitsStr = to_string_dec_uint(entry.bits);
line += SubGhzDView::pad_string_with_spaces(5 - bitsStr.length()) + bitsStr;
@@ -303,6 +361,29 @@ void atomo_decrypt(uint8_t* buff) {
}
}
bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt(uint32_t decrypt) {
uint16_t end_serial = serial & 0xFF;
if ((decrypt >> 28 == btn) && (((((uint16_t)(decrypt >> 16)) & 0xFF) == end_serial) ||
((((uint16_t)(decrypt >> 16)) & 0xFF) == 0))) {
cnt = decrypt & 0xFFFF;
return true;
}
return false;
}
bool SubGhzDRecentEntryDetailView::keeloq_check_decrypt_centurion(uint32_t decrypt) {
if ((decrypt >> 28 == btn) && ((((uint16_t)(decrypt >> 16)) & 0x3FF) == 0x1CE)) {
cnt = decrypt & 0xFFFF;
return true;
}
return false;
}
const uint32_t came_twee_magic_numbers_xor[15] = {
0x0E0E0E00,
0x1D1D1D11,
@@ -341,7 +422,7 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
if (entry_.sensorType == FPS_CAMEATOMO) {
entry_.data ^= 0xFFFFFFFFFFFFFFFF;
entry_.data <<= 4;
uint8_t pack[8] = {};
uint8_t pack[8];
pack[0] = (entry_.data >> 56);
pack[1] = ((entry_.data >> 48) & 0xFF);
pack[2] = ((entry_.data >> 40) & 0xFF);
@@ -591,7 +672,57 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
}
if (entry_.sensorType == FPS_KEELOQ) {
// too many sub protocol versions, skipping. maybe in future when we'll have much more fw space
uint64_t data_rev = FProtoGeneral::subghz_protocol_blocks_reverse_key(entry_.data, 64);
btn = data_rev >> 60;
serial = (data_rev >> 32) & 0xFFFFFFF;
fix = data_rev >> 32;
encrypted = data_rev & 0xFFFFFFFF;
KeeloqKeystore keystore{};
const auto& keys = keystore.get_keys();
if (keys.empty()) {
return;
}
for (const auto& key : keys) {
switch (key.type) {
case KEELOQ_SIMPLE_LEARNING: {
uint32_t decrypted = keeloq_decrypt(encrypted, key.key);
if (keeloq_check_decrypt(decrypted)) {
mf_name = key.mf_name;
hop = decrypted;
return;
}
break;
}
case KEELOQ_NORMAL_LEARNING: {
uint64_t man = keeloq_normal_learning(fix, key.key);
uint32_t decrypted = keeloq_decrypt(encrypted, man);
if (key.mf_name == "Centurion" && keeloq_check_decrypt_centurion(decrypted)) {
mf_name = "Centurion";
hop = decrypted;
return;
} else if (keeloq_check_decrypt(decrypted)) {
mf_name = key.mf_name;
hop = decrypted;
return;
}
break;
}
}
}
return;
}
@@ -758,5 +889,18 @@ void SubGhzDRecentEntryDetailView::parseProtocol() {
btn = entry_.data & 0xf;
return;
}
if (entry_.sensorType == FPS_HOLTEKHT6P20B) {
serial = entry_.data >> 8;
btn = (entry_.data >> 4) & 0xF;
return;
}
if (entry_.sensorType == FPS_RESTAURANT_PAGER) {
// 25-bit EV1527-variant: [sysid:16][pager:4][func:4][stop:1]
serial = (entry_.data >> 9) & 0xFFFF; // System ID
// btn/cnt left as SD_NO values; friendly output in update_data()
return;
}
}
} // namespace ui
} // namespace ui
+36 -12
View File
@@ -25,7 +25,11 @@
#define SD_NO_SERIAL 0xFFFFFFFF
#define SD_NO_BTN 0xFF
#define SD_NO_CNT 0xFF
#define SD_NO_CNT 0xFFFFFFFF
#define SD_NO_HOP 0xFFFFFFFF
#define SD_NO_FIX 0xFFFFFFFF
#define SD_NO_ENCRYPTED 0xFFFFFFFF
#include "ui.hpp"
#include "ui_navigation.hpp"
@@ -122,15 +126,18 @@ class SubGhzDView : public View {
SubGhzDRecentEntries recent{};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
SignalToken signal_token_tick_second{};
@@ -149,8 +156,8 @@ class SubGhzDView : public View {
std::unique_ptr<SubGhzDLogger> logger{};
const RecentEntriesColumns columns{{
{"Type", 19},
RecentEntriesColumns columns{{
{"Type", 0},
{"Bits", 4},
{"Age", 3},
}};
@@ -188,22 +195,39 @@ class SubGhzDRecentEntryDetailView : public View {
uint32_t cnt = SD_NO_CNT;
uint32_t seed = 0;
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"};
// keeloq specific
std::string keeloq_file_buffer{};
uint32_t hop = SD_NO_HOP;
uint32_t fix = SD_NO_FIX;
uint32_t encrypted = SD_NO_ENCRYPTED;
std::string mf_name = "Unknown";
bool keeloq_check_decrypt(uint32_t decrypt);
bool keeloq_check_decrypt_centurion(uint32_t decrypt);
// end keeloq specific
Text text_type{{UI_POS_X(0), 1 * 16, 15 * 8, 16}, "?"};
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Console console{
{0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
Labels labels{
{{0 * 8, 0 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Serial: ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Data:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 2 * 16}, "Serial: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Data:", Theme::getInstance()->fg_light->foreground},
};
Button button_done{
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
"Done"};
Button button_save{
{4, screen_height - 32 - 12, 96, 32},
"Save"};
void parseProtocol();
};
+7 -7
View File
@@ -76,29 +76,29 @@ class TestView : public View {
bool logging{false};
Labels labels{
{{0 * 8, 1 * 16}, "Data:", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), 1 * 16}, "Data:", Theme::getInstance()->fg_light->foreground}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 8},
{UI_POS_X(0), 0 * 8},
nav_};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4},
};
Text text_debug_a{
{0 * 8, 4 * 16, screen_width, 16},
{UI_POS_X(0), 4 * 16, screen_width, 16},
"..."};
Text text_debug_b{
{0 * 8, 5 * 16, screen_width, 16},
{UI_POS_X(0), 5 * 16, screen_width, 16},
"..."};
Button button_cal{
+99 -21
View File
@@ -26,6 +26,7 @@
#include "log_file.hpp"
#include "string_format.hpp"
#include "irq_controls.hpp"
using namespace portapack;
namespace fs = std::filesystem;
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
paint_cursor(painter);
}
void TextViewer::enable_long_press() {
// Enable long press on "Select".
SwitchesState config;
config[toUType(Switch::Sel)] = true;
set_switches_long_press_config(config);
}
void TextViewer::on_focus() {
enable_long_press();
}
bool TextViewer::on_key(const KeyEvent key) {
int16_t delta_col = 0;
int16_t delta_line = 0;
if (key == KeyEvent::Left)
delta_col = -1;
else if (key == KeyEvent::Right)
delta_col = 1;
else if (key == KeyEvent::Up)
delta_line = -1;
else if (key == KeyEvent::Down)
delta_line = 1;
else if (key == KeyEvent::Select && on_select) {
on_select();
return true;
if (key == KeyEvent::Select) {
// Toggle 'digit' mode with long-press.
if (digit_mode_ || key_is_long_pressed(key)) {
digit_mode_ = !digit_mode_;
set_dirty();
return true;
}
}
if (digit_mode_) {
switch (key) {
case KeyEvent::Left:
delta_col = -1;
break;
case KeyEvent::Right:
delta_col = 1;
break;
case KeyEvent::Up:
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
break;
case KeyEvent::Down:
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
break;
default:
return false;
}
if (delta_col == 0 && delta_line == 0) {
return true;
}
} else {
switch (key) {
case KeyEvent::Left:
delta_col = -1;
break;
case KeyEvent::Right:
delta_col = 1;
break;
case KeyEvent::Up:
delta_line = -1;
break;
case KeyEvent::Down:
delta_line = 1;
break;
case KeyEvent::Select: {
if (on_select) {
on_select();
return true;
}
break;
}
default:
return false;
}
}
// Always allow cursor direction to be updated.
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
bool TextViewer::on_encoder(EncoderEvent delta) {
bool updated = false;
if (cursor_.dir == ScrollDirection::Horizontal)
updated = apply_scrolling_constraints(0, delta);
else {
delta *= 16;
updated = apply_scrolling_constraints(delta, 0);
if (digit_mode_) {
if (delta > 0) {
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
} else if (delta < 0) {
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
}
updated = true;
} else {
if (cursor_.dir == ScrollDirection::Horizontal) {
updated = apply_scrolling_constraints(0, delta);
} else {
delta *= 16;
updated = apply_scrolling_constraints(delta, 0);
}
if (updated) {
redraw();
}
}
if (updated)
redraw();
return updated;
}
void TextViewer::set_value(uint8_t new_value) {
if (new_value != value_) {
value_ = new_value;
if (on_change) {
on_change(value_);
}
set_dirty();
}
}
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
paint_state_.redraw_text = redraw_text;
paint_state_.redraw_marked = redraw_marked;
@@ -349,7 +427,7 @@ void TextViewer::set_font_zoom(bool zoom) {
/* TextEditorMenu ***************************************************/
TextEditorMenu::TextEditorMenu()
: View{{7 * 4, 9 * 4, 25 * 8, 25 * 8}} {
: View{{UI_POS_X_CENTER(25), 9 * 4, 25 * 8, 25 * 8}} {
add_children(
{
&rect_frame,
@@ -534,7 +612,7 @@ void TextEditorView::open_file(const fs::path& path) {
viewer.clear_file();
delete_temp_file(path_);
path_ = {};
path_ = "";
file_dirty_ = false;
has_temp_file_ = false;
auto result = FileWrapper::open(
+12 -5
View File
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
std::function<void()> on_select{};
std::function<void()> on_cursor_moved{};
std::function<void(uint8_t)> on_change{};
void paint(Painter& painter) override;
bool on_key(KeyEvent key) override;
bool on_encoder(EncoderEvent delta) override;
void set_value(uint8_t new_value);
void on_focus() override;
void redraw(bool redraw_text = false, bool redraw_marked = false);
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
void cursor_set(uint16_t line, uint16_t col);
void cursor_mark_selected();
void cursor_clear_marked();
void enable_long_press();
typedef std::pair<uint16_t, uint16_t> LineColPair;
std::vector<LineColPair> lineColPair{};
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
int8_t char_height{};
uint8_t max_line{};
uint8_t max_col{};
bool digit_mode_{false};
uint8_t value_{0};
bool allow_digit_mode_{true};
/* Returns true if the cursor was updated. */
bool apply_scrolling_constraints(
@@ -157,7 +164,7 @@ class TextEditorMenu : public View {
void hide_children(bool hidden);
Rectangle rect_frame{
{0 * 8, 0 * 8, 23 * 8, 23 * 8},
{UI_POS_X(0), 0 * 8, 23 * 8, 23 * 8},
Theme::getInstance()->fg_dark->foreground};
NewButton button_home{
@@ -260,23 +267,23 @@ class TextEditorView : public View {
TextViewer viewer{
/* 272 = screen_height - 16 (top bar) - 32 (bottom controls) */
{0, 0, screen_width, 272}};
{0, 0, screen_width, UI_POS_HEIGHT_REMAINING(4)}};
TextEditorMenu menu{};
NewButton button_menu{
{26 * 8, 34 * 8, 4 * 8, 4 * 8},
{UI_POS_X_RIGHT(4), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH(4), UI_POS_HEIGHT(2)},
{},
&bitmap_icon_controls,
Theme::getInstance()->bg_dark->background,
/*vcenter*/ true};
Text text_position{
{0 * 8, 34 * 8, 26 * 8, 2 * 8},
{UI_POS_X(0), UI_POS_Y_BOTTOM(3), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT(1)},
""};
Text text_size{
{0 * 8, 36 * 8, 26 * 8, 2 * 8},
{UI_POS_X(0), UI_POS_Y_BOTTOM(2), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT(1)},
""};
};
@@ -81,13 +81,13 @@ class TouchCalibrationView : public View {
Color::black()};
Image image_calibrate_1{
{240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32},
{screen_width - 32 - 16, (screen_height - 16) / 2 - 16, 32, 32},
&bitmap_target_calibrate,
Color::white(),
Color::black()};
Image image_calibrate_2{
{240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32},
{screen_width / 2 - 16, (screen_height - 16) - 32 - 16, 32, 32},
&bitmap_target_calibrate,
Color::white(),
Color::black()};
@@ -99,13 +99,13 @@ class TouchCalibrationView : public View {
Color::black()};
Image image_verify_1{
{240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32},
{screen_width - 32 - 16, (screen_height - 16) / 2 - 16, 32, 32},
&bitmap_target_verify,
Color::white(),
Color::black()};
Image image_verify_2{
{240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32},
{screen_width / 2 - 16, (screen_height - 16) - 32 - 16, 32, 32},
&bitmap_target_verify,
Color::white(),
Color::black()};
@@ -131,11 +131,11 @@ class TouchCalibrationView : public View {
"Calibration failed. Retry?"};
Button button_cancel{
{40, 200, 64, 24},
{40, 200, UI_POS_WIDTH(8), 24},
"Cancel"};
Button button_ok{
{136, 200, 64, 24},
{136, 200, UI_POS_WIDTH(8), 24},
"OK"};
void on_frame_sync();
+4 -4
View File
@@ -162,16 +162,16 @@ class TouchTunesView : public View {
{{18 * 8 + 4, 18 * 8}, "V"},
{{22 * 8, 18 * 8}, "F4"},
{{0 * 8, 5 * 8}, "1"},
{{UI_POS_X(0), 5 * 8}, "1"},
{{4 * 8, 5 * 8}, "2"},
{{8 * 8, 5 * 8}, "3"},
{{0 * 8, 10 * 8}, "4"},
{{UI_POS_X(0), 10 * 8}, "4"},
{{4 * 8, 10 * 8}, "5"},
{{8 * 8, 10 * 8}, "6"},
{{0 * 8, 15 * 8}, "7"},
{{UI_POS_X(0), 15 * 8}, "7"},
{{4 * 8, 15 * 8}, "8"},
{{8 * 8, 15 * 8}, "9"},
{{0 * 8, 20 * 8}, "*"},
{{UI_POS_X(0), 20 * 8}, "*"},
{{4 * 8, 20 * 8}, "0"},
{{8 * 8, 20 * 8}, "#"},
+40 -12
View File
@@ -107,6 +107,7 @@ void WeatherView::focus() {
WeatherView::WeatherView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&channel,
&field_rf_amp,
&field_lna,
&field_vga,
@@ -196,6 +197,10 @@ WeatherView::~WeatherView() {
baseband::shutdown();
}
uint16_t WeatherView::bcd_decode_short(uint32_t data) {
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
}
const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
switch (type) {
case FPW_NexusTH:
@@ -224,6 +229,8 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
return "LaCrosse TX141THBv2";
case FPW_OREGON2:
return "Oregon2";
case FPW_OREGON2B:
return "Oregon2B";
case FPW_OREGON3:
return "Oregon3";
case FPW_OREGONv1:
@@ -269,16 +276,13 @@ void RecentEntriesTable<ui::WeatherRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
const Style& style,
RecentEntriesColumns& columns) {
std::string line{};
line.reserve(30);
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType);
if (line.length() < 10) {
line += WeatherView::pad_string_with_spaces(10 - line.length());
} else {
line = truncate(line, 10);
}
line.resize(columns.at(0).second, ' ');
std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 1));
std::string humStr = (entry.humidity != WS_NO_HUMIDITY) ? to_string_dec_uint(entry.humidity) + "%" : "-";
@@ -301,7 +305,9 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
WeatherRecentEntry ret = {};
ret.sensorType = data->sensorType;
int16_t i16 = 0;
int32_t i32 = 0;
uint16_t u16 = 0;
uint64_t u64 = 0;
uint8_t u8 = 0;
uint8_t channel_3021[] = {3, 0, 2, 1};
@@ -444,12 +450,34 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
ret.humidity = (data->decode_data >> 8) & 0xFF;
break;
case FPW_OREGON2:
i16 = ((data->decode_data >> 4) & 0xF) * 10 + (((data->decode_data >> 4) >> 4) & 0xF);
i16 *= 10;
i16 += (data->decode_data >> 12) & 0xF;
if (data->decode_data & 0xF) i16 = -i16;
ret.temp = (float)i16 / 10.0;
// todo fix missing parts.
i32 = bcd_decode_short(data->decode_data >> 4);
i32 *= 10;
i32 += (data->decode_data >> 12) & 0xf;
if (data->decode_data & 0xF) i32 = -i32;
ret.temp = (float)i32 / 10.0;
ret.battery_low = ((data->decode_data >> 32) & 0x4) ? 1 : 0;
u8 = (data->decode_data >> 44) & 0xF;
ret.channel = 1;
while (u8 > 1) {
ret.channel++;
u8 >>= 1;
}
break;
case FPW_OREGON2B:
ret.humidity = bcd_decode_short(data->decode_data);
u64 = data->decode_data >> 8;
i32 = bcd_decode_short(u64 >> 4);
i32 *= 10;
i32 += (u64 >> 12) & 0xf;
if (u64 & 0xF) i32 = -i32;
ret.temp = (float)i32 / 10.0;
ret.battery_low = ((data->decode_data >> 32) & 0x4) ? 1 : 0;
u8 = (data->decode_data >> 44) & 0xF;
ret.channel = 1;
while (u8 > 1) {
ret.channel++;
u8 >>= 1;
}
break;
case FPW_OREGON3:
// todo check
+21 -17
View File
@@ -114,6 +114,7 @@ class WeatherView : public View {
private:
void on_tick_second();
uint16_t bcd_decode_short(uint32_t data);
void on_data(const WeatherDataMessage* data);
WeatherRecentEntry process_data(const WeatherDataMessage* data);
NavigationView& nav_;
@@ -134,25 +135,28 @@ class WeatherView : public View {
WeatherRecentEntries recent{};
OptionsField options_temperature{
{10 * 8, 0 * 16},
{10 * 8, UI_POS_Y(0)},
2,
{{STR_DEGREES_C, 0},
{STR_DEGREES_F, 1}}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{13 * 8, UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{15 * 8, UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{18 * 8, UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
Channel channel{
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
};
AudioVolumeField field_volume{
{screen_width - 2 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
SignalToken signal_token_tick_second{};
@@ -171,8 +175,8 @@ class WeatherView : public View {
std::unique_ptr<WeatherLogger> logger{};
const RecentEntriesColumns columns{{
{"Type", 10},
RecentEntriesColumns columns{{
{"Type", 0},
{"Temp", 5},
{"Hum", 4},
{"Ch", 3},
@@ -215,14 +219,14 @@ class WeatherRecentEntryDetailView : public View {
Text text_age{{10 * 8, 7 * 16, 10 * 8, 16}, "?"};
Labels labels{
{{0 * 8, 0 * 16}, "Weather Station", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 1 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 2 * 16}, "Id: ", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 3 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 4 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 5 * 16}, "Channel:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 6 * 16}, "Battery:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Age:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(0)}, "Weather Station", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 1 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 2 * 16}, "Id: ", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 3 * 16}, "Temp:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 4 * 16}, "Humidity:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 5 * 16}, "Channel:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 6 * 16}, "Battery:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), 7 * 16}, "Age:", Theme::getInstance()->fg_light->foreground},
};
Button button_done{
+9 -2
View File
@@ -225,8 +225,15 @@ volume_range_t volume_range() {
return audio_codec->headphone_gain_range();
}
void set_volume(const volume_t volume) {
audio_codec->set_headphone_volume(volume);
bool set_volume(const volume_t volume) {
// add retry method
for (int i = 0; i < 100; ++i) {
if (audio_codec->set_headphone_volume(volume)) {
return true;
}
chThdSleepMilliseconds(2);
}
return false;
}
} /* namespace headphone */
+2 -2
View File
@@ -49,7 +49,7 @@ class Codec {
virtual void headphone_enable() = 0;
virtual void headphone_disable() = 0;
virtual volume_range_t headphone_gain_range() const = 0;
virtual void set_headphone_volume(const volume_t volume) = 0;
virtual bool set_headphone_volume(const volume_t volume) = 0;
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;
@@ -93,7 +93,7 @@ namespace headphone {
volume_range_t volume_range();
void set_volume(const volume_t volume);
bool set_volume(const volume_t volume);
} /* namespace headphone */
+133 -10
View File
@@ -21,6 +21,7 @@
*/
#include "baseband_api.hpp"
#include <cstring>
#include "audio.hpp"
#include "tonesets.hpp"
@@ -52,12 +53,21 @@ static void send_message(const Message* const message) {
creg::m0apptxevent::assert_event();
if constexpr (check_for_message_hang) {
#ifdef PRALINE
/* Timeout: ~3 seconds at typical clock speeds */
auto count = 200'000'000u;
#else
auto count = UINT32_MAX;
#endif
while (shared_memory.baseband_message && --count)
/* spin */;
if (count == 0)
#ifdef PRALINE
chDbgPanic("BB Msg Timeout");
#else
chDbgPanic("Baseband Send Fail");
#endif
} else {
while (shared_memory.baseband_message)
/* spin */;
@@ -147,6 +157,19 @@ void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration) {
send_message(&message);
}
void set_sstvrx_data(const uint8_t code) {
const SSTVRXConfigureMessage message{
code};
send_message(&message);
}
void set_sstvrx_phase_slant(const int16_t phase, const int16_t slant) {
const SSTVRXPhaseSlantMessage message{
phase,
slant};
send_message(&message);
}
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) {
const AFSKRxConfigureMessage message{
baudrate,
@@ -186,13 +209,14 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
send_message(&message);
}
void set_fsk(const size_t deviation) {
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes) {
const FSKRxConfigureMessage message{
taps_200k_decim_0,
taps_16k0_decim_1,
taps_11k0_channel,
2,
deviation};
samplesPerSymbol,
syncWord,
syncWordLength,
preamble,
preambleLength,
numDataBytes};
send_message(&message);
}
@@ -287,8 +311,8 @@ void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit,
send_message(&message);
}
void set_pocsag() {
const POCSAGConfigureMessage message{};
void set_pocsag(int8_t baud_config) {
const POCSAGConfigureMessage message{baud_config};
send_message(&message);
}
@@ -311,12 +335,22 @@ void set_rds_data(const uint16_t message_length) {
send_message(&message);
}
void set_spectrum(const size_t sampling_rate, const size_t trigger) {
void set_spectrum(
const size_t sampling_rate,
const size_t trigger) {
const WidebandSpectrumConfigMessage message{
sampling_rate, trigger};
send_message(&message);
}
void set_time_sink(
const size_t sampling_rate,
const size_t trigger) {
const TimeSinkConfigMessage message{
sampling_rate, trigger};
send_message(&message);
}
void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
const WeFaxRxConfigureMessage message{lpm, ioc};
send_message(&message);
@@ -327,6 +361,21 @@ void set_noaaapt_config() {
send_message(&message);
}
void set_vor_config(bool enabled) {
const VorRxConfigureMessage message{enabled};
send_message(&message);
}
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled, const std::string& ident, bool enabled) {
const VorTxConfigureMessage message{radial_deg, ident_enabled, ident.c_str(), enabled};
send_message(&message);
}
void set_flex_config() {
const FlexConfigureMessage message{};
send_message(&message);
}
void set_siggen_tone(const uint32_t tone) {
const SigGenToneMessage message{
TONES_F2D(tone, TONES_SAMPLERATE)};
@@ -349,8 +398,77 @@ void set_subghzd_config(uint8_t modulation = 0, uint32_t sampling_rate = 0) {
send_message(&message);
}
void set_moreserx_config(uint8_t mode) {
const MorseRXConfigureMessage message{mode};
send_message(&message);
}
void set_tonedetect_config(uint8_t squelch, uint32_t ctcss_freq_x10) {
const ToneDetectConfigureMessage message{squelch, ctcss_freq_x10};
send_message(&message);
}
void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta) {
const MorseTXConfigureMessage message{mode, tone, fm_delta};
send_message(&message);
}
void set_morsetx_key(bool key_down) {
const MorseTXkeyMessage message{key_down};
send_message(&message);
}
void set_bitstream_config(uint32_t deviation, uint8_t mode) {
const StreamTXConfigurationMessage message{deviation, mode};
send_message(&message);
}
void set_rtty_config(uint16_t baud, uint16_t shift, uint8_t* payload, uint16_t payload_length) {
RTTYDataMessage message{baud, shift};
if (payload && payload_length > 0) {
message.data_len = payload_length > message.max_len ? message.max_len : payload_length;
for (size_t i = 0; i < message.data_len; ++i) {
message.data[i] = payload[i];
}
}
send_message(&message);
}
void set_rtty_config(RTTYDataMessage& message) {
send_message(&message);
}
void set_epirb_tx_config(EPIRBTXDataMessage& message) {
send_message(&message);
}
void set_epirb_rx_config(EPIRBRXConfig& message) {
send_message(&message);
}
void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length) {
const size_t max_len = sizeof(shared_memory.bb_data.data);
if (frame_length > max_len) frame_length = max_len;
memcpy(shared_memory.bb_data.data, dibits, frame_length);
P25TxConfigureMessage msg{};
msg.frame_length = frame_length;
send_message(&msg);
}
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start) {
HunterConfigMessage message{};
message.energy_threshold = threshold;
message.hangtime_ms = hangtime_ms;
message.start = start;
send_message(&message);
}
static bool baseband_image_running = false;
bool is_image_running() {
return baseband_image_running;
}
void run_image(const spi_flash::image_tag_t image_tag) {
if (baseband_image_running) {
chDbgPanic("BBRunning");
@@ -410,7 +528,12 @@ void shutdown() {
send_message(&message);
shared_memory.application_queue.reset();
// Allow time for the shutdown message to be processed and for the baseband
// core to stop before starting another image. Otherwise, the M4 may still be
// running and cause a crash when the next image is started.
#ifdef PRALINE
chThdSleepMilliseconds(20);
#endif
baseband_image_running = false;
}
+25 -3
View File
@@ -74,13 +74,15 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone();
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
void set_sstvrx_data(const uint8_t code);
void set_sstvrx_phase_slant(const int16_t phase, const int16_t slant);
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
void set_fifo_data(const int8_t* data);
void set_pitch_rssi(int32_t avg, bool enabled);
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
void kill_afsk();
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
void set_fsk(const size_t deviation);
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
void set_aprs(const uint32_t baudrate);
void set_btlerx(uint8_t channel_number);
@@ -91,23 +93,43 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
void set_ook_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint8_t repeat, const uint32_t pause_symbols, const uint8_t de_bruijn_length = 0);
void kill_ook();
void set_fsk_data(const uint32_t stream_length, const uint32_t samples_per_bit, const uint32_t shift, const uint32_t progress_notice);
void set_pocsag();
void set_pocsag(int8_t baud_config = -1);
void set_adsb();
void set_jammer(const bool run, const jammer::JammerType type, const uint32_t speed);
void set_rds_data(const uint16_t message_length);
void set_spectrum(const size_t sampling_rate, const size_t trigger);
void set_spectrum(
const size_t sampling_rate,
const size_t trigger);
void set_time_sink(
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 set_moreserx_config(uint8_t mode);
void set_tonedetect_config(uint8_t squelch, uint32_t ctcss_freq_x10 = 0);
void set_morsetx_config(uint8_t mode, uint32_t tone, float fm_delta);
void set_morsetx_key(bool key_down);
void set_wefax_config(uint8_t lpm, uint8_t ioc);
void set_noaaapt_config();
void set_vor_config(bool enabled = true);
void set_vor_tx_config(uint16_t radial_deg, bool ident_enabled = true, const std::string& ident = "", bool enabled = true);
void set_flex_config();
void set_bitstream_config(uint32_t deviation, uint8_t mode); // mode 0 for am, 1 for 2fsk
void set_rtty_config(uint16_t baud, uint16_t shift, uint8_t* payload = nullptr, uint16_t payload_length = 0); // baud*100
void set_rtty_config(RTTYDataMessage& message);
void set_epirb_tx_config(EPIRBTXDataMessage& message);
void set_epirb_rx_config(EPIRBRXConfig& message);
void set_p25tx_data(const uint8_t* dibits, uint16_t frame_length);
void set_hunter_config(uint32_t threshold, uint32_t hangtime_ms, bool start);
void request_roger_beep();
void request_rssi_beep();
void request_beep_stop();
void request_audio_beep(uint32_t freq, uint32_t sample_rate, uint32_t duration_ms);
bool is_image_running();
void run_image(const portapack::spi_flash::image_tag_t image_tag);
void run_prepared_image(const uint32_t m4_code);
void shutdown();
+41 -3
View File
@@ -415,6 +415,44 @@ static constexpr Bitmap bitmap_icon_adsb{
{16, 16},
bitmap_icon_adsb_data};
static constexpr uint8_t bitmap_icon_adulttoys_data[] = {
0x00,
0x00,
0x30,
0x0C,
0x78,
0x1E,
0x7C,
0x3E,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFE,
0x7F,
0xFC,
0x3F,
0xFC,
0x3F,
0xF8,
0x1F,
0xF0,
0x0F,
0xE0,
0x07,
0xC0,
0x03,
0x80,
0x01,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_adulttoys{
{16, 16},
bitmap_icon_adulttoys_data};
static constexpr uint8_t bitmap_icon_ais_data[] = {
0x00,
0x01,
@@ -4259,7 +4297,7 @@ static constexpr Bitmap bitmap_icon_tpms{
{16, 16},
bitmap_icon_tpms_data};
static constexpr uint8_t bitmap_icon_tranceivers_data[] = {
static constexpr uint8_t bitmap_icon_transceivers_data[] = {
0x80,
0x01,
0xC0,
@@ -4293,9 +4331,9 @@ static constexpr uint8_t bitmap_icon_tranceivers_data[] = {
0xFF,
0xFF,
};
static constexpr Bitmap bitmap_icon_tranceivers{
static constexpr Bitmap bitmap_icon_transceivers{
{16, 16},
bitmap_icon_tranceivers_data};
bitmap_icon_transceivers_data};
static constexpr uint8_t bitmap_icon_transmit_data[] = {
0x80,
+705 -35
View File
@@ -23,6 +23,9 @@
#include "portapack_persistent_memory.hpp"
#include "portapack_io.hpp"
#include "portapack.hpp"
#include "lpc43xx.inc"
#include "hackrf_hal.hpp"
using namespace hackrf::one;
@@ -30,8 +33,25 @@ using namespace hackrf::one;
#include "lpc43xx_cpp.hpp"
using namespace lpc43xx;
#ifdef PRALINE
#include "hackrf_gpio.hpp"
#include "gpio.hpp"
#endif
#ifdef PRALINE
extern "C" {
#include "fpga_bridge.h"
}
// Need access to ssp1_arbiter from radio namespace for FPGA related radio method dependencies.
#include "radio.hpp"
#endif
constexpr uint32_t si5351_vco_f = 800000000;
#ifdef PRALINE
constexpr uint32_t si5351_vco_afe_f = 800000000; // If necessary may be changed to 768 MHz for optimal for 3.072 MHz sample frequencies commonly used by apps
#endif
constexpr si5351::Inputs si5351_inputs{
.f_xtal = si5351_xtal_f,
.f_clkin = si5351_clkin_f,
@@ -53,7 +73,49 @@ constexpr si5351::PLL si5351_pll_xtal_25m{
.b = 0,
.c = 1,
};
#ifdef PRALINE
// Define pll_a from 25MHz clock for stable PLL A, and AFE locked reference
// PLL A: 800 MHz VCO (32x Multiplier for jitter-free 3.072 MHz sampling)
constexpr si5351::PLL si5351_pll_a_afe_800m{
.f_in = si5351_inputs.f_xtal, // 25,000,000 Hz
.a = 32, // Multiplier: 25 * 32 = 800
.b = 0,
.c = 1,
};
// PLL A: registers (Base 34) 800 MHz VCO (For jitter-free AFE sampling frequencies)
constexpr auto si5351_pll_a_800_reg = si5351_pll_a_afe_800m.reg(0); // Base 26
static_assert(si5351_pll_a_afe_800m.f_vco() == si5351_vco_f, "PLL A XTAL frequency wrong");
static_assert(si5351_pll_a_afe_800m.p1() == 3584, "PLL A XTAL P1 wrong");
static_assert(si5351_pll_a_afe_800m.p2() == 0, "PLL A XTAL P2 wrong");
static_assert(si5351_pll_a_afe_800m.p3() == 1, "PLL A XTAL P3 wrong");
// Define pll_b 25MHz clock for stable PLL B
// PLL B: 800 MHz VCO (32x Multiplier for (For stable Digital/SGPIO bus)
constexpr si5351::PLL si5351_pll_b_800m{
.f_in = si5351_inputs.f_xtal, // 25,000,000 Hz
.a = 32, // Multiplier: 25 * 32 = 800
.b = 0,
.c = 1,
};
// PLL B: registers (Base 34) 800 MHz VCO (For stable Digital/SGPIO bus)
constexpr auto si5351_pll_b_800_reg = si5351_pll_b_800m.reg(1); // Base 34
static_assert(si5351_pll_b_800m.f_vco() == si5351_vco_f, "PLL B XTAL frequency wrong");
static_assert(si5351_pll_b_800m.p1() == 3584, "PLL B XTAL P1 wrong");
static_assert(si5351_pll_b_800m.p2() == 0, "PLL B XTAL P2 wrong");
static_assert(si5351_pll_b_800m.p3() == 1, "PLL B XTAL P3 wrong");
#else
// PLL A registers (Base 26)
constexpr auto si5351_pll_a_xtal_reg = si5351_pll_xtal_25m.reg(0);
static_assert(si5351_pll_xtal_25m.f_vco() == si5351_vco_f, "PLL XTAL frequency wrong");
static_assert(si5351_pll_xtal_25m.p1() == 3584, "PLL XTAL P1 wrong");
static_assert(si5351_pll_xtal_25m.p2() == 0, "PLL XTAL P2 wrong");
static_assert(si5351_pll_xtal_25m.p3() == 1, "PLL XTAL P3 wrong");
#endif
constexpr si5351::PLL si5351_pll_clkin_10m{
.f_in = si5351_inputs.f_clkin_out(),
@@ -64,11 +126,6 @@ constexpr si5351::PLL si5351_pll_clkin_10m{
constexpr auto si5351c_pll_b_clkin_reg = si5351_pll_clkin_10m.reg(1);
constexpr auto si5351a_pll_a_clkin_reg = si5351_pll_clkin_10m.reg(0);
static_assert(si5351_pll_xtal_25m.f_vco() == si5351_vco_f, "PLL XTAL frequency wrong");
static_assert(si5351_pll_xtal_25m.p1() == 3584, "PLL XTAL P1 wrong");
static_assert(si5351_pll_xtal_25m.p2() == 0, "PLL XTAL P2 wrong");
static_assert(si5351_pll_xtal_25m.p3() == 1, "PLL XTAL P3 wrong");
static_assert(si5351_pll_clkin_10m.f_vco() == si5351_vco_f, "PLL CLKIN frequency wrong");
static_assert(si5351_pll_clkin_10m.p1() == 9728, "PLL CLKIN P1 wrong");
static_assert(si5351_pll_clkin_10m.p2() == 0, "PLL CLKIN P2 wrong");
@@ -82,16 +139,16 @@ constexpr si5351::MultisynthFractional si5351_ms_18m432 {
.r_div = 1,
};
*/
/*
constexpr si5351::MultisynthFractional si5351_ms_0_20m {
.f_src = si5351_vco_f,
.a = 20,
.b = 0,
.c = 1,
.r_div = 1,
// constexpr auto si5351_ms_0_20m_reg = si5351_ms_0_20m.reg(0);
constexpr si5351::MultisynthFractional si5351_ms_0_4m{
.f_src = si5351_vco_f, // 800,000,000 Hz
.a = 100, // Integer divider 100
.b = 0,
.c = 1,
.r_div = 1 // Final R-divider: 2^1 = 2
};
constexpr auto si5351_ms_0_20m_reg = si5351_ms_0_20m.reg(0);
*/
constexpr si5351::MultisynthFractional si5351_ms_0_8m{
.f_src = si5351_vco_f,
.a = 50,
@@ -99,8 +156,91 @@ constexpr si5351::MultisynthFractional si5351_ms_0_8m{
.c = 1,
.r_div = 1,
};
#ifdef PRALINE
// (initial 4 MHz from 800 MHz VCO: 800 / 200 = 4)
constexpr si5351::MultisynthFractional si5351_ms_afe_4m{
.f_src = si5351_vco_afe_f, // 800 MHz
.a = 200,
.b = 0,
.c = 1,
.r_div = 0};
// (10 MHz from 800 MHz VCO: 800 / 80 = 10)
constexpr si5351::MultisynthFractional si5351_ms_afe_10m{
.f_src = si5351_vco_afe_f, // 800 MHz
.a = 80,
.b = 0,
.c = 1,
.r_div = 0};
// (20 MHz from 800 MHz VCO: 800 / 40 = 20)
constexpr si5351::MultisynthFractional si5351_ms_afe_20m{
.f_src = si5351_vco_afe_f,
.a = 40,
.b = 0,
.c = 1,
.r_div = 0,
};
// (25 MHz from 800 MHz VCO: 800 / 32 = 25)
// Define xtal MultiSynth 25MHz clock for stable PLL A, and AFE locked XTAL reference
constexpr si5351::MultisynthFractional si5351_ms_afe_25m{
.f_src = si5351_vco_afe_f, // 800,000,000 Hz
.a = 32, // 800 / 32 = 25 MHz
.b = 0,
.c = 1,
.r_div = 0 // No final bit-shifting
};
// (40 MHz from 800 MHz VCO: 800 / 20 = 40)
constexpr si5351::MultisynthFractional si5351_ms_afe_40m{
.f_src = si5351_vco_afe_f, // 800 MHz
.a = 20,
.b = 0,
.c = 1,
.r_div = 0};
// (20 MHz from 800 MHz VCO: 800 / 40 = 20)
constexpr si5351::MultisynthFractional si5351_ms_20m{
.f_src = si5351_vco_f,
.a = 40,
.b = 0,
.c = 1,
.r_div = 0,
};
// constexpr auto si5351_ms_20m_reg = si5351_ms_20m.reg(0);
// (25 MHz from 800 MHz VCO: 800 / 32 = 25)
// Define xtal MultiSynth 25MHz clock for stable PLL B, and XTAL reference
constexpr si5351::MultisynthFractional si5351_ms_25m{
.f_src = si5351_vco_f, // 800,000,000 Hz
.a = 32, // 800 / 32 = 25 MHz
.b = 0,
.c = 1,
.r_div = 0 // No final bit-shifting
};
#endif
constexpr auto si5351c_ms_0_8m_reg = si5351_ms_0_8m.reg(clock_generator_output_og_codec);
#ifdef PRALINE
// Verify compile-time values for 8 MHz config
static_assert(si5351_ms_0_8m.p1() == 5888, "MS0 8MHz P1 should be 5888 (0x1700)");
static_assert(si5351_ms_0_8m.p2() == 0, "MS0 8MHz P2 should be 0");
static_assert(si5351_ms_0_8m.p3() == 1, "MS0 8MHz P3 should be 1");
static_assert(si5351_ms_0_8m.f_out() == 8000000, "MS0 should output 8 MHz");
// Verify register array encoding
static_assert(si5351c_ms_0_8m_reg[0] == 42, "MS0 base register should be 42");
static_assert(si5351c_ms_0_8m_reg[1] == 0x00, "MS0 reg43 P3[15:8] should be 0x00");
static_assert(si5351c_ms_0_8m_reg[2] == 0x01, "MS0 reg44 P3[7:0] should be 0x01");
static_assert(si5351c_ms_0_8m_reg[3] == 0x10, "MS0 reg45 R_DIV should be 0x10");
static_assert(si5351c_ms_0_8m_reg[4] == 0x17, "MS0 reg46 P1[15:8] should be 0x17");
static_assert(si5351c_ms_0_8m_reg[5] == 0x00, "MS0 reg47 P1[7:0] should be 0x00");
#endif
constexpr si5351::MultisynthFractional si5351_ms_group{
.f_src = si5351_vco_f,
.a = 80, /* Don't care */
@@ -186,12 +326,27 @@ static_assert(si5351a_ms_int_mcu_clkin.f_out() == mcu_clkin_r9_f, "MS int MCU CL
using namespace si5351;
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
#ifdef PRALINE
static constexpr ClockControl::MultiSynthSource get_si5351a_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
return (reference_source == ClockManager::ReferenceSource::Xtal)
? ClockControl::MultiSynthSource::PLLA
: ClockControl::MultiSynthSource::PLLB;
}
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
return (reference_source == ClockManager::ReferenceSource::Xtal)
? ClockControl::MultiSynthSource::PLLA
: ClockControl::MultiSynthSource::PLLB;
}
#else
static constexpr ClockControl::MultiSynthSource get_si5351c_reference_clock_generator_pll(const ClockManager::ReferenceSource reference_source) {
return (reference_source == ClockManager::ReferenceSource::Xtal)
? ClockControl::MultiSynthSource::PLLA
: ClockControl::MultiSynthSource::PLLB;
}
#endif
#ifndef PRALINE
constexpr ClockControls si5351c_clock_control_common{{
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, get_si5351c_reference_clock_generator_pll(ClockManager::ReferenceSource::Xtal), ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_Off},
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Group, ClockControl::ClockInvert::Invert, get_si5351c_reference_clock_generator_pll(ClockManager::ReferenceSource::Xtal), ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
@@ -202,17 +357,43 @@ constexpr ClockControls si5351c_clock_control_common{{
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, get_si5351c_reference_clock_generator_pll(ClockManager::ReferenceSource::Xtal), ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_Off},
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, get_si5351c_reference_clock_generator_pll(ClockManager::ReferenceSource::Xtal), ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
}};
#endif
constexpr ClockControls si5351a_clock_control_common{{
#ifdef PRALINE
// CLK0: MAX5864 (ADC) - 4mA, Normal PLLA Integer
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
// CLK1: SCT_CLK (iCE40 FPGA) - 4mA, Normal PLLB Integer
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
// CLK2: LPC43xx MCU - 2mA, Normal PLLA (Must be Integer for MCU stability)
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
// CLK3: CLKOUT SMA Port P1 - 2mA, Normal PLLB Integer Power_Off
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
// CLK4: MAX2831 reference (40 MHz) - 4mA, Invert PLLA Integer (Required for mixer lock)
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
// CLK5: RFFC5072 reference (40 MHz) - 4mA, Invert PLLA Integer (Required for mixer lock)
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Invert, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_On},
// CLK6: Not used (disabled) 2mA, Normal PLLB Integer, Power_Off
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
// CLK7: Not used (disabled) 2mA, Normal PLLB Integer, Power_Off
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLB, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
#else
{ClockControl::ClockCurrentDrive::_6mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
{ClockControl::ClockCurrentDrive::_4mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Fractional, ClockControl::ClockPowerDown::Power_Off},
{ClockControl::ClockCurrentDrive::_8mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
// CLK4: HackRF r9 - not inverted
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
// CLK5: HackRF r9 - not used
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
// CLK6: Not used
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
// CLK7: Not used
{ClockControl::ClockCurrentDrive::_2mA, ClockControl::ClockSource::MS_Self, ClockControl::ClockInvert::Normal, ClockControl::MultiSynthSource::PLLA, ClockControl::MultiSynthMode::Integer, ClockControl::ClockPowerDown::Power_Off},
#endif
}};
ClockManager::Reference ClockManager::get_reference() const {
return reference;
}
@@ -238,17 +419,23 @@ std::string ClockManager::get_freq() {
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
}
static void portapack_tcxo_enable() {
void ClockManager::portapack_tcxo_enable() {
#ifdef PRALINE
gpio_control::clkin_ctrl.setActive();
set_p1_control(P1_Function::P22_ClkIn);
#endif
portapack::io.reference_oscillator(true);
/* Delay >10ms at 96MHz clock speed for reference oscillator to start. */
/* Delay an additional 1ms (arbitrary) for the clock generator to detect a signal. */
volatile uint32_t delay = 240000 + 24000;
while (delay--)
;
while (delay--);
}
static void portapack_tcxo_disable() {
void ClockManager::portapack_tcxo_disable() {
#ifdef PRALINE
gpio_control::clkin_ctrl.setInactive();
#endif
portapack::io.reference_oscillator(false);
}
@@ -259,6 +446,48 @@ void ClockManager::init_clock_generator() {
clock_generator.reset();
clock_generator.set_crystal_internal_load_capacitance(CrystalInternalLoadCapacitance::XTAL_CL_8pF);
clock_generator.enable_fanout();
#ifdef PRALINE
constexpr size_t clock_generator_output_pro_mcu_clkin = 2;
auto si5351_clock_control_common = si5351a_clock_control_common;
/*
* Match the HackRF One init flow:
* 1. PLLA stays on the local 25MHz crystal.
* 2. PLLB is configured from the Si5351 CLKIN pin.
* 3. CLK2 temporarily drives GP_CLKIN directly from CLKIN so we can
* detect whether P22 is carrying a valid 10MHz reference.
* 4. Once the source is chosen, all runtime clocks are switched to the
* selected PLL and the working multisynths are programmed.
*/
clock_generator.set_pll_input_sources(si5351c_pll_input_sources);
clock_generator.set_clock_control(
clock_generator_output_pro_mcu_clkin,
si5351_clock_control_common[clock_generator_output_pro_mcu_clkin]
.clk_src(ClockControl::ClockSource::CLKIN)
.clk_pdn(ClockControl::ClockPowerDown::Power_On));
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
chThdSleepMilliseconds(20);
reference = choose_reference();
clock_generator.disable_output(clock_generator_output_pro_mcu_clkin);
const auto ref_pll =
get_si5351c_reference_clock_generator_pll(reference.source);
const ClockControls si5351_clock_control = ClockControls{{
si5351_clock_control_common[0].ms_src(ref_pll),
si5351_clock_control_common[1].ms_src(ref_pll),
si5351_clock_control_common[2].ms_src(ref_pll),
si5351_clock_control_common[3].ms_src(ref_pll),
si5351_clock_control_common[4].ms_src(ref_pll),
si5351_clock_control_common[5].ms_src(ref_pll),
si5351_clock_control_common[6].ms_src(ref_pll),
si5351_clock_control_common[7].ms_src(ref_pll),
}};
clock_generator.set_clock_control_single_byte(si5351_clock_control);
#else
clock_generator.set_pll_input_sources(hackrf_r9
? si5351a_pll_input_sources
: si5351c_pll_input_sources);
@@ -299,7 +528,54 @@ void ClockManager::init_clock_generator() {
si5351_clock_control_common[7].ms_src(ref_pll),
}};
clock_generator.set_clock_control(si5351_clock_control);
#endif
#ifdef PRALINE
clock_generator.write_pll_single_byte(0, si5351_pll_a_afe_800m);
{
const auto& pll_b = si5351c_pll_b_clkin_reg;
for (size_t i = 1; i < pll_b.size(); i++) {
clock_generator.write_register(pll_b[0] + i - 1, pll_b[i]);
}
}
// CLK0: DAFE_CLK initial 4 MHz
clock_generator.write_ms_single_byte(
0,
si5351_ms_afe_4m);
// CLK1: DSCT_CLK initial 10 MHz
clock_generator.write_ms_single_byte(
1,
si5351_ms_10m);
// CLK2: MCU_CLK 40 MHz
clock_generator.write_ms_single_byte(
2,
si5351_ms_afe_40m);
// CLK3: P2 clock mux, disabled below
clock_generator.write_ms_single_byte(
3,
si5351_ms_10m);
// CLK4: DXCVR_CLK 40 MHz
clock_generator.write_ms_single_byte(
4,
si5351_ms_afe_40m);
// CLK5: DMIX_CLK 40 MHz
clock_generator.write_ms_single_byte(
5,
si5351_ms_afe_40m);
{
const auto& ms6_7 = si5351a_ms6_7_off_reg;
for (size_t i = 1; i < ms6_7.size(); i++) {
clock_generator.write_register(ms6_7[0] + i - 1, ms6_7[i]);
}
}
#else
if (hackrf_r9) {
const PLLReg pll_reg = (reference.source == ReferenceSource::Xtal)
? si5351_pll_a_xtal_reg
@@ -320,22 +596,36 @@ void ClockManager::init_clock_generator() {
clock_generator.write(si5351c_ms_5_reg);
clock_generator.write(si5351c_ms6_7_off_mcu_clkin_reg);
}
#endif
clock_generator.reset_plls();
// Wait for PLL(s) to lock.
#ifdef PRALINE
uint8_t device_status_mask =
(ref_pll == ClockControl::MultiSynthSource::PLLB) ? 0x40 : 0x20;
uint32_t pll_timeout = 100000;
while ((clock_generator.device_status() & device_status_mask) != 0 && pll_timeout > 0) {
pll_timeout--;
}
clock_generator.enable_output(clock_generator_output_pro_mcu_clkin);
#else
// Wait for PLL(s) to lock - with timeout to prevent hang
uint8_t device_status_mask = hackrf_r9
? 0x20
: (ref_pll == ClockControl::MultiSynthSource::PLLB)
? 0x40
: 0x20;
while ((clock_generator.device_status() & device_status_mask) != 0)
;
while ((clock_generator.device_status() & device_status_mask) != 0);
clock_generator.set_clock_control(
clock_generator_output_mcu_clkin,
si5351_clock_control_common[clock_generator_output_mcu_clkin].ms_src(ref_pll).clk_pdn(ClockControl::ClockPowerDown::Power_On));
clock_generator.enable_output(clock_generator_output_mcu_clkin);
#endif
}
uint32_t ClockManager::measure_gp_clkin_frequency() {
@@ -346,12 +636,16 @@ uint32_t ClockManager::measure_gp_clkin_frequency() {
}
bool ClockManager::loss_of_signal() {
#ifdef PRALINE
return clock_generator.clkin_loss_of_signal();
#else
if (hackrf_r9) {
const auto frequency = measure_gp_clkin_frequency();
return (frequency < 9850000) || (frequency > 10150000);
} else {
return clock_generator.clkin_loss_of_signal();
}
#endif
}
ClockManager::ReferenceSource ClockManager::detect_reference_source() {
@@ -374,11 +668,21 @@ ClockManager::ReferenceSource ClockManager::detect_reference_source() {
}
ClockManager::Reference ClockManager::choose_reference() {
#ifdef PRALINE
const auto detected_reference = detect_reference_source();
if ((detected_reference == ReferenceSource::External) ||
(detected_reference == ReferenceSource::PortaPack)) {
const auto frequency = measure_gp_clkin_frequency();
if ((frequency >= 9850000) && (frequency <= 10150000)) {
return {detected_reference, 10000000};
}
}
#else
if (hackrf_r9) {
gpio_r9_clkin_en.write(1);
gpio_control::r9_clkin_en.setActive();
volatile uint32_t delay = 240000 + 24000;
while (delay--)
;
while (delay--);
}
const auto detected_reference = detect_reference_source();
@@ -391,8 +695,9 @@ ClockManager::Reference ClockManager::choose_reference() {
}
if (hackrf_r9) {
gpio_r9_clkin_en.write(0);
gpio_control::r9_clkin_en.setInactive();
}
#endif
portapack_tcxo_disable();
return {ReferenceSource::Xtal, 25000000};
@@ -403,6 +708,15 @@ void ClockManager::shutdown() {
}
void ClockManager::enable_codec_clocks() {
#ifdef PRALINE
/* PRALINE: only gate the clocks owned by the RF datapath here.
* CLK2 is MCU_CLKIN and must stay independent of runtime RX/TX clock
* management or the SGPIO/GPIO subsystem can glitch mid-stream. */
clock_generator.enable_clock(clock_generator_output_og_codec); /* CLK0 MAX5864*/
clock_generator.enable_clock(clock_generator_output_og_cpld); /* CLK1 iCE40 FPGA*/
clock_generator.enable_output_mask(
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
#else
if (hackrf_r9) {
clock_generator.enable_clock(clock_generator_output_r9_sgpio);
} else {
@@ -420,6 +734,7 @@ void ClockManager::enable_codec_clocks() {
clock_generator.enable_output_mask(
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld) | (1U << clock_generator_output_og_sgpio));
}
#endif
}
void ClockManager::disable_codec_clocks() {
@@ -427,6 +742,13 @@ void ClockManager::disable_codec_clocks() {
* be enabled for the output to come to rest at the state specified by
* CLKx_DISABLE_STATE.
*/
#ifdef PRALINE
/* PRALINE: leave CLK2/MCU_CLKIN alone; only disable datapath-owned clocks. */
clock_generator.disable_output_mask(
(1U << clock_generator_output_og_codec) | (1U << clock_generator_output_og_cpld));
clock_generator.disable_clock(clock_generator_output_og_codec);
clock_generator.disable_clock(clock_generator_output_og_cpld);
#else
if (hackrf_r9) {
clock_generator.disable_output_mask(1U << clock_generator_output_r9_sgpio);
clock_generator.disable_clock(clock_generator_output_r9_sgpio);
@@ -437,9 +759,17 @@ void ClockManager::disable_codec_clocks() {
clock_generator.disable_clock(clock_generator_output_og_cpld);
clock_generator.disable_clock(clock_generator_output_og_sgpio);
}
#endif
}
void ClockManager::enable_if_clocks() {
#ifdef PRALINE
/* PRALINE uses CLK5 (first IF) and CLK4 (second IF) */
clock_generator.enable_clock(clock_generator_output_og_first_if);
clock_generator.enable_output_mask(1U << clock_generator_output_og_first_if);
clock_generator.enable_clock(clock_generator_output_og_second_if);
clock_generator.enable_output_mask(1U << clock_generator_output_og_second_if);
#else
if (hackrf_r9) {
clock_generator.enable_clock(clock_generator_output_r9_if);
clock_generator.enable_output_mask(1U << clock_generator_output_r9_if);
@@ -449,9 +779,16 @@ void ClockManager::enable_if_clocks() {
clock_generator.enable_clock(clock_generator_output_og_second_if);
clock_generator.enable_output_mask(1U << clock_generator_output_og_second_if);
}
#endif
}
void ClockManager::disable_if_clocks() {
#ifdef PRALINE
clock_generator.disable_output_mask(1U << clock_generator_output_og_first_if);
clock_generator.disable_clock(clock_generator_output_og_first_if);
clock_generator.disable_output_mask(1U << clock_generator_output_og_second_if);
clock_generator.disable_clock(clock_generator_output_og_second_if);
#else
if (hackrf_r9) {
clock_generator.disable_output_mask(1U << clock_generator_output_r9_if);
clock_generator.disable_clock(clock_generator_output_r9_if);
@@ -461,19 +798,139 @@ void ClockManager::disable_if_clocks() {
clock_generator.disable_output_mask(1U << clock_generator_output_og_second_if);
clock_generator.disable_clock(clock_generator_output_og_second_if);
}
#endif
}
void ClockManager::set_sampling_frequency(const uint32_t frequency) {
#ifdef PRALINE
/*
* PRALINE sample rate strategy:
* 1. Maximize AFE rate to push Nyquist above MAX2831's 11.6 MHz LPF minimum
* 2. Use FPGA decimation/interpolation to achieve desired output rate
* 3. Ensure AFE rate is achievable by Si5351 (clean division from 800 MHz VCO)
*/
constexpr uint32_t MAX_AFE_RATE = 40000000; // Use 40 MHz per GSG reference
constexpr uint8_t MAX_N = 5;
_base_band_frequency = frequency;
uint8_t n = 0;
uint32_t afe_rate = frequency;
// Find the largest n where AFE rate stays within limit
// Start at n=0 and work up
while (n < MAX_N) {
uint32_t next_rate = afe_rate << 1;
if (next_rate > MAX_AFE_RATE) break;
afe_rate = next_rate;
n++;
}
/* The TX gateware only implements interpolation factors x1..x16
* (tx_intrp 0..4). The rate search above can select n == 5 (x32) for very
* low TX sample rates. If we kept afe_rate/CLK0/CLK1 at the x32 rate while
* the FPGA only interpolated by x16, the TX datapath would be clocked at
* twice the gateware's output rate, doubling the waveform speed. Cap the
* AFE rate and _resampling_n to the x16 limit in TX so the clocks and the
* interpolation setting stay aligned. */
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
constexpr uint8_t MAX_TX_N = 4; // x16, matches max tx_intrp
if (n > MAX_TX_N) {
n = MAX_TX_N;
afe_rate = frequency << n;
}
}
_resampling_n = n;
radio::invalidate_spi_config();
// Configure Si5351 clocks using the correct AFE VCO
// CLK0 always drives the AFE/MAX5864 clock.
clock_generator.set_ms_frequency(0, afe_rate * 2, si5351_vco_afe_f, 1);
/* Do not reset PLLA here. On PRALINE, CLK2/MCU_CLKIN is sourced from PLLA,
* so a runtime PLLA reset can glitch the LPC43xx peripheral clock tree and
* break SGPIO-driven RX apps such as ADSB/APRS. Updating the multisynths is
* sufficient for sample-rate changes. */
if (radio::debug::get_cached_direction() == rf::Direction::Transmit) {
/*
* TX path:
* Baseband generators such as AFSK still synthesize samples at their
* legacy logical sample rates (for APRS this is 1.536MHz). On PRALINE,
* the FPGA must interpolate those samples up to the active AFE rate.
*
* The TX datapath is clocked from CLK1/fpgaclk. Therefore CLK1 must
* equal the desired complex sample rate at the DAC side. Driving CLK1
* at 2x the intended TX sample rate causes the entire APRS waveform to
* run twice as fast, which matches the "energy present, undecodable"
* symptom seen on HackRF One receivers.
*
* Gateware mapping from standard.py:
* tx_intrp = 0 -> x1
* tx_intrp = 1 -> x2
* tx_intrp = 2 -> x4
* tx_intrp = 3 -> x8
* tx_intrp = 4 -> x16
*/
if (fpga_get_mode() != FPGA_MODE_TX) {
fpga_set_mode(FPGA_MODE_TX);
}
// TX gateware consumes complex samples at the rate presented on CLK1.
clock_generator.set_ms_frequency(1, afe_rate, si5351_vco_afe_f, 0);
uint8_t tx_interp = 0;
uint32_t tx_rate = frequency;
while ((tx_rate < afe_rate) && (tx_interp < 4)) {
tx_rate <<= 1;
tx_interp++;
}
fpga_tx_set_interpolation(tx_interp);
fpga_tx_set_nco_enable(false);
fpga_tx_set_phase_step(0);
}
// for RX mode
else {
// RX path keeps CLK1 at 2x AFE for receive timing / SGPIO alignment.
clock_generator.set_ms_frequency(1, afe_rate * 2, si5351_vco_afe_f, 0);
// === Stop FPGA processing and flush filters ===
fpga_debug_register_write(1, 0x00); // Disable FPGA filters (resets CIC accumulators)
if (fpga_get_mode() != FPGA_MODE_RX) {
fpga_set_mode(FPGA_MODE_RX);
}
// Set FPGA RX decimation register
fpga_debug_register_write(FPGA_REG_DECIM, n);
/* RX Mode: Register 3 is FPGA_REG_RX_DIGITAL_GAIN.
* We shift up by (3 * n) to compensate for CIC bit-growth.
*/
uint8_t ds = (3 * n);
ds += 2;
fpga_debug_register_write(FPGA_REG_RX_DIGITAL_GAIN, ds);
// Re-enable FPGA processing with clean state ===
fpga_debug_register_write(1, 0x01);
}
#else
/* Codec clock is at sampling frequency, CPLD and SGPIO clocks are at
* twice the frequency, and derived from the MS0 synth. So it's only
* necessary to change the MS0 synth frequency, and ensure the output
* is divided by two.
*/
if (hackrf_r9) {
clock_generator.set_ms_frequency(clock_generator_output_r9_sgpio, frequency * 2, si5351_vco_f, 0);
} else {
clock_generator.set_ms_frequency(clock_generator_output_og_codec, frequency * 2, si5351_vco_f, 1);
}
#endif
}
void ClockManager::set_reference_ppb(const int32_t ppb) {
@@ -481,10 +938,21 @@ void ClockManager::set_reference_ppb(const int32_t ppb) {
* It is assumed an external clock coming in to CLKIN/PLLB is sufficiently accurate as to not need adjustment.
* TODO: Revisit the above policy. It may be good to allow adjustment of the external reference too.
*/
#ifdef PRALINE
// On Praline, only apply if we aren't locked to a superior external 10MHz source
// (Assuming you have a way to detect the 10MHz presence on Praline)
if (reference.source == ReferenceSource::External) {
return;
}
constexpr uint32_t pll_multiplier = si5351_pll_a_afe_800m.a;
#else
if (hackrf_r9 && reference.source != ReferenceSource::Xtal) {
return;
}
constexpr uint32_t pll_multiplier = si5351_pll_xtal_25m.a;
#endif
constexpr uint32_t denominator = 1000000 / pll_multiplier;
const uint32_t new_a = (ppb >= 0) ? pll_multiplier : (pll_multiplier - 1);
const uint32_t new_b = (ppb >= 0) ? (ppb / 1000) : (denominator + (ppb / 1000));
@@ -496,8 +964,13 @@ void ClockManager::set_reference_ppb(const int32_t ppb) {
.b = new_b,
.c = new_c,
};
#ifdef PRALINE
clock_generator.write_pll_single_byte(0, pll);
#else
const auto pll_a_reg = pll.reg(0);
clock_generator.write(pll_a_reg);
#endif
}
void ClockManager::start_frequency_monitor_measurement(const cgu::CLK_SEL clk_sel) {
@@ -512,9 +985,16 @@ void ClockManager::start_frequency_monitor_measurement(const cgu::CLK_SEL clk_se
}
void ClockManager::wait_For_frequency_monitor_measurement_done() {
// FREQ_MON mechanism fails to finish if there's no clock present on selected input?!
while (LPC_CGU->FREQ_MON.MEAS == 1)
;
// FREQ_MON mechanism fails to finish if there's no clock present on selected input?!
#ifdef PRALINE
// PRALINE FIX: Add timeout to prevent infinite hang
uint32_t timeout = 100000;
while (LPC_CGU->FREQ_MON.MEAS == 1 && timeout > 0) {
timeout--;
}
#else
while (LPC_CGU->FREQ_MON.MEAS == 1);
#endif
}
uint32_t ClockManager::get_frequency_monitor_measurement_in_hertz() {
@@ -525,6 +1005,91 @@ uint32_t ClockManager::get_frequency_monitor_measurement_in_hertz() {
}
void ClockManager::start_audio_pll() {
#ifdef PRALINE
// Control Block
cgu::pll0audio::ctrl({
.pd = 1, // Start powered down
.bypass = 0, // Use the PLL
.directi = 0, // Enable N-divider
.directo = 0, // Enable P-divider
.clken = 0, // Disable output initially
.frm = 0, // Normal mode
.autoblock = 1, // Glitchless switching
.pllfract_req = 1, // Integer, Disabled
.sel_ext = 1, // Use GP_CLKIN (CLK2)
.mod_pd = 0, // Modulator OFF
.clk_sel = cgu::CLK_SEL::GP_CLKIN,
});
/*
* Audio PLL Configuration for 48 kHz audio with 256Fs MCLK
* Target output: Fout = 12.288 MHz
*
* Formulas:
* Fout = Fin × MSEL / (NSEL × PSEL)
* FCO = 2 × Fin × MSEL / NSEL (must be 275-550 MHz)
*/
/*
*
* 10 MHz INPUT (HackRF r9 compatible but interfere with fm band)
*
* MSEL=3072, NSEL=125, PSEL=20
* Fout = 10 × 3072 / (125 × 20) = 30720 / 2500 = 12.288 MHz
* FCO = 2 × 10 × 3072 / 125 = 61440 / 125 = 491.52 MHz
*
* Encoded values: MDEC=8308, NDEC=45, PDEC=31
* CLK2 harmonics: 90, 100, 110 MHz (interfere with FM band!)
*
*/
/*
// Math: (10 MHz * 3072) / (125 * 20) * 2 = 12.288MHz
cgu::pll0audio::mdiv({.mdec = 8308UL}); // MSEL = 3072
cgu::pll0audio::np_div({
.pdec = 31, // PSEL = 20 for 10 MHz
.ndec = 45 // NSEL = 125 for 10 MHz
});
*/
/*
*
* 40 MHz INPUT (Recommended - avoids FM band harmonics)
*
* MSEL=768, NSEL=125, PSEL=20
* Fout = 40 × 768 / (125 × 20) = 30720 / 2500 = 12.288 MHz
* FCO = 2 × 40 × 768 / 125 = 61440 / 125 = 491.52 MHz
*
* Encoded values: MDEC=30542, NDEC=45, PDEC=31
* CLK2 harmonics: 80, 120, 160 MHz (none in FM 88-108 MHz band)
*
*/
// 40 MHz input → 12.288 MHz output (same as HackRF OG)
// Math: (40MHz * 768) / (125 * 20) * 2 = 12.288MHz
cgu::pll0audio::mdiv({.mdec = 30542UL}); // MSEL = 768
cgu::pll0audio::np_div({.pdec = 31,
.ndec = 45});
cgu::pll0audio::frac({.pllfract_ctrl = 0});
cgu::pll0audio::power_up();
// Lock and Routing (Keep as is)
{
uint32_t timeout = 100000;
while (!cgu::pll0audio::is_locked() && timeout > 0) {
timeout--;
}
}
cgu::pll0audio::clock_enable();
set_base_audio_clock_divider(1);
LPC_CGU->BASE_AUDIO_CLK.AUTOBLOCK = 1;
LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IDIVD);
#else
cgu::pll0audio::ctrl({
.pd = 1,
.bypass = 0,
@@ -559,14 +1124,15 @@ void ClockManager::start_audio_pll() {
});
cgu::pll0audio::power_up();
while (!cgu::pll0audio::is_locked())
;
cgu::pll0audio::clock_enable();
while (!cgu::pll0audio::is_locked());
cgu::pll0audio::clock_enable();
set_base_audio_clock_divider(1);
LPC_CGU->BASE_AUDIO_CLK.AUTOBLOCK = 1;
LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IDIVD);
#endif
}
void ClockManager::set_base_audio_clock_divider(const size_t divisor) {
@@ -575,16 +1141,48 @@ void ClockManager::set_base_audio_clock_divider(const size_t divisor) {
}
void ClockManager::stop_audio_pll() {
#ifdef PRALINE
/* PRALINE: Gracefully switch audio peripherals away from the PLL branch */
LPC_CGU->BASE_AUDIO_CLK.PD = 1; // Power down the branch first
LPC_CGU->BASE_AUDIO_CLK.CLK_SEL = toUType(cgu::CLK_SEL::IRC); // Reset to safe IRC source
#endif
cgu::pll0audio::clock_disable();
cgu::pll0audio::power_down();
while (cgu::pll0audio::is_locked())
;
#ifdef PRALINE
/* PRALINE: Add a safety timeout to the unlock check to prevent potential hangs */
uint32_t timeout = 100000;
while (cgu::pll0audio::is_locked() && timeout > 0) {
timeout--;
}
#else
while (cgu::pll0audio::is_locked());
#endif
}
void ClockManager::enable_clock_output(bool enable) {
#ifdef PRALINE
auto clkout_select = clock_generator_output_og_clkout;
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 = si5351a_clock_control_common;
const auto ref_pll = get_si5351a_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());
}
#else
if (hackrf_r9) {
gpio_r9_clkout_en.output();
gpio_r9_clkout_en.write(enable);
gpio_control::r9_clkout_en.setState(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.
@@ -610,4 +1208,76 @@ void ClockManager::enable_clock_output(bool enable) {
clock_generator.disable_output(clkout_select);
clock_generator.set_clock_control(clkout_select, ClockControl::power_off());
}
#endif
}
#ifdef PRALINE
void ClockManager::set_p1_control(P1_Function func) {
// Truth table based on P1_Control.csv (L=setInactive, H=setActive)
switch (func) {
case P1_Function::TriggerIn:
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::AuxClk1:
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setActive();
break;
case P1_Function::ClkIn:
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::TriggerOut:
gpio_control::p1_ctrl2.setInactive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setActive();
break;
case P1_Function::P22_ClkIn:
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::P2_5:
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setInactive();
gpio_control::p1_ctrl0.setActive();
break;
case P1_Function::NotConnected:
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setInactive();
break;
case P1_Function::AuxClk2:
gpio_control::p1_ctrl2.setActive();
gpio_control::p1_ctrl1.setActive();
gpio_control::p1_ctrl0.setActive();
break;
}
}
void ClockManager::set_p2_control(P2_Function func) {
// Ensure all P2 control pins are configured as outputs
// Truth table based on P2_Control.csv (L=setInactive, H=setActive)
switch (func) {
case P2_Function::Clk3:
// CTRL0 is 'X' (don't care) according to CSV, we default it to Low (setInactive)
gpio_control::p2_ctrl1.setInactive();
gpio_control::p2_ctrl0.setInactive();
break;
case P2_Function::TriggerIn:
gpio_control::p2_ctrl1.setActive();
gpio_control::p2_ctrl0.setInactive();
break;
case P2_Function::TriggerOut:
gpio_control::p2_ctrl1.setActive();
gpio_control::p2_ctrl0.setActive();
break;
}
}
#endif
+45
View File
@@ -70,8 +70,44 @@ class ClockManager {
void set_sampling_frequency(const uint32_t frequency);
uint32_t get_sampling_frequency() const {
return _base_band_frequency;
};
void set_reference_ppb(const int32_t ppb);
#ifdef PRALINE
enum class P1_Function {
TriggerIn, // CTRL2: L, CTRL1: L, CTRL0: L
AuxClk1, // CTRL2: L, CTRL1: L, CTRL0: H
ClkIn, // CTRL2: L, CTRL1: H, CTRL0: L
TriggerOut, // CTRL2: L, CTRL1: H, CTRL0: H
P22_ClkIn, // CTRL2: H, CTRL1: L, CTRL0: L
P2_5, // CTRL2: H, CTRL1: L, CTRL0: H
NotConnected, // CTRL2: H, CTRL1: H, CTRL0: L (NC)
AuxClk2 // CTRL2: H, CTRL1: H, CTRL0: H
};
// Multiplexer 2 (P2) Functions - Based on P2_Control.csv
enum class P2_Function {
Clk3, // CTRL1: L, CTRL0: X (Don't care)
TriggerIn, // CTRL1: H, CTRL0: L
TriggerOut // CTRL1: H, CTRL0: H
};
// Function declarations
void set_p1_control(P1_Function func);
void set_p2_control(P2_Function func);
uint8_t get_resampling_n() const { return _resampling_n; }
// Si5351 diagnostic methods
uint8_t si5351_read_status() { return clock_generator.device_status(); }
uint8_t si5351_read_register(uint8_t reg) { return clock_generator.read_register(reg); }
void si5351_write_register(uint8_t reg, uint8_t value) { clock_generator.write_register(reg, value); }
#endif
uint32_t get_frequency_monitor_measurement_in_hertz();
Reference get_reference() const;
@@ -80,11 +116,16 @@ class ClockManager {
void enable_clock_output(bool enable);
void portapack_tcxo_enable();
void portapack_tcxo_disable();
private:
I2C& i2c0;
si5351::Si5351& clock_generator;
Reference reference;
uint32_t _base_band_frequency{20000000};
void set_gp_clkin_to_clkin_direct();
void start_frequency_monitor_measurement(const cgu::CLK_SEL clk_sel);
@@ -99,6 +140,10 @@ class ClockManager {
ReferenceSource detect_reference_source();
Reference choose_reference();
bool loss_of_signal();
#ifdef PRALINE
uint8_t _resampling_n{0}; // Current decimation factor (log2)
#endif
};
#endif /*__CLOCK_MANAGER_H__*/
+19 -15
View File
@@ -24,6 +24,11 @@
#include "hackrf_gpio.hpp"
#include "portapack_hal.hpp"
#include "board.h"
#include "gpio.hpp"
using namespace gpio_control;
void config_mode_blink_until_dfu();
void config_mode_set() {
@@ -56,7 +61,6 @@ uint32_t blink_patterns[] = {
void config_mode_run() {
configure_pins_portapack();
portapack::gpio_dfu.input();
portapack::persistent_memory::cache::init();
if (hackrf_r9) {
@@ -75,14 +79,14 @@ void config_mode_run() {
config_mode_blink_until_dfu();
}
auto last_dfu_btn = portapack::gpio_dfu.read();
auto last_dfu_btn = dfu_button.read();
int32_t counter = 0;
int8_t blink_pattern_value = portapack::persistent_memory::config_cpld() +
(portapack::persistent_memory::config_disable_external_tcxo() ? 5 : 0);
while (true) {
auto dfu_btn = portapack::gpio_dfu.read();
auto dfu_btn = dfu_button.read();
auto dfu_clicked = last_dfu_btn == true && dfu_btn == false;
last_dfu_btn = dfu_btn;
@@ -103,16 +107,16 @@ void config_mode_run() {
auto tx_value = ((tx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
if (tx_value) {
hackrf::one::led_tx.on();
led_tx.setActive();
} else {
hackrf::one::led_tx.off();
led_tx.setInactive();
}
auto rx_value = ((rx_blink_pattern >> ((counter >> 0) & 31)) & 0x1) == 0x1;
if (rx_value) {
hackrf::one::led_rx.on();
led_rx.setActive();
} else {
hackrf::one::led_rx.off();
led_rx.setInactive();
}
chThdSleepMilliseconds(100);
@@ -122,17 +126,17 @@ void config_mode_run() {
void config_mode_blink_until_dfu() {
while (true) {
hackrf::one::led_tx.on();
hackrf::one::led_rx.on();
hackrf::one::led_usb.on();
led_tx.setActive();
led_rx.setActive();
led_usb.setActive();
chThdSleepMilliseconds(10);
hackrf::one::led_tx.off();
hackrf::one::led_rx.off();
hackrf::one::led_usb.off();
led_tx.setInactive();
led_rx.setInactive();
led_usb.setInactive();
chThdSleepMilliseconds(115);
auto dfu_btn = portapack::gpio_dfu.read();
auto dfu_btn = dfu_button.read();
if (dfu_btn)
break;
}
@@ -140,7 +144,7 @@ void config_mode_blink_until_dfu() {
while (true) {
chThdSleepMilliseconds(10);
auto dfu_btn = portapack::gpio_dfu.read();
auto dfu_btn = dfu_button.read();
if (!dfu_btn)
break;
}
+11
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
return (result);
}
int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
file_path = macaddress_dir / u"macaddress.db";
index_item_length = 7;
record_length = 64;
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
return (result);
}
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
if (search_term.empty())
return DATABASE_RECORD_NOT_FOUND;
+8 -1
View File
@@ -2,6 +2,7 @@
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2022 Arjan Onwezen
* Copyright (C) 2025 Tommaso Ventafridda
*
* This file is part of PortaPack.
*
@@ -60,6 +61,12 @@ class database {
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
struct MacAddressDBRecord {
char vendor_name[64]; // vendor/manufacturer name
};
int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
private:
std::filesystem::path file_path = ""; // path including filename
int index_item_length = 0; // length of index item
@@ -77,4 +84,4 @@ class database {
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
};
#endif /*__DATABASE_H__*/
#endif /*__DATABASE_H__*/
+8 -8
View File
@@ -33,6 +33,9 @@
#include "irq_controls.hpp"
#include "file_path.hpp"
#include "gpio.hpp"
using namespace gpio_control;
using namespace ui;
#define DEBUG_LOG_FILE "debug_log.txt"
@@ -134,18 +137,16 @@ void draw_line(int32_t y_offset, const char* label, regarm_t value) {
}
void runtime_error(uint8_t source) {
LED led = (source == CORTEX_M0) ? hackrf::one::led_rx : hackrf::one::led_tx;
const auto& led = (source == CORTEX_M0) ? led_rx : led_tx;
led.off();
led.setInactive();
// wait for DFU button release if pressed, so we don't immediately jump into stack dump
while (swizzled_switches() & (1 << (int)Switch::Dfu))
;
while (swizzled_switches() & (1 << (int)Switch::Dfu));
while (true) {
volatile size_t n = 1000000U;
while (n--)
;
while (n--);
led.toggle();
// Stack dump will cover entire screen, so wait for DFU button press to attempt it
@@ -225,8 +226,7 @@ void draw_stack_dump() {
// Out of room on the screen or end of stack - allow Up/Down paging.
// First wait for button release from previous press.
// NOTE: can't call swizzle_switches() with interrupted enabled!
while (swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu)))
;
while (swizzled_switches() & ((1 << (int)Switch::Right) | (1 << (int)Switch::Left) | (1 << (int)Switch::Down) | (1 << (int)Switch::Up) | (1 << (int)Switch::Sel) | (1 << (int)Switch::Dfu)));
painter.draw_string({border, portapack::display.height() - border - 8}, *Theme::getInstance()->bg_darkest_small, "Use UP/DOWN key");
+1 -2
View File
@@ -41,8 +41,7 @@ extern uint32_t __process_stack_end__;
inline uint32_t get_free_stack_space() {
uint32_t* p;
for (p = &__process_stack_base__; *p == CRT0_STACKS_FILL_PATTERN && p < &__process_stack_end__; p++)
;
for (p = &__process_stack_base__; *p == CRT0_STACKS_FILL_PATTERN && p < &__process_stack_end__; p++);
auto stack_space_left = p - &__process_stack_base__;
return stack_space_left;
+210 -22
View File
@@ -37,8 +37,20 @@
#include "ch.h"
#include "lpc43xx_cpp.hpp"
using namespace lpc43xx;
#include "gpio.hpp"
using namespace gpio_control;
#include "irq_rtc.hpp"
#include "i2c_lld.h"
#include "i2cdevmanager.hpp"
#include "i2cdev_ppmod.hpp"
#include "lpc43xx.inc"
#include "nvic.h"
#include "lpc43xx_m0.h"
#include "rffc507x_spi.hpp"
#include <array>
@@ -124,17 +136,196 @@ void EventDispatcher::request_stop() {
void EventDispatcher::set_display_sleep(const bool sleep) {
// TODO: Distribute display sleep message more broadly, shut down data generation
// on baseband side, since all that data is being discarded during sleep.
// on baseband side, since all that data is being discarded during sleep. -- DON'T TODO it, sincethe stealth mode want to send with screen off!
if (sleep) {
portapack::backlight()->off();
portapack::display.sleep();
portapack::display.sleep(false); // when called the hw_sleep = true, the irq wont fire, so the EVT_MASK_LCD_FRAME_SYNC won't set.
} else {
portapack::display.wake();
portapack::display.wake(true); // not important, command not affect if already hw waken up
// Don't turn on backlight here.
// Let frame sync handler turn on backlight after repaint.
}
EventDispatcher::display_sleep = sleep;
};
}
void EventDispatcher::charge_deep_sleep(const bool sleep) {
bool detect = false;
uint8_t valid_mask = 0;
uint8_t percent = 0;
uint16_t voltage = 0;
int32_t current = 0;
constexpr I2CConfig i2c_config_12mhz{
.high_count = 15,
.low_count = 15,
};
if (sleep) {
auto dev = (i2cdev::I2cDev_PPmod*)i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD);
if (dev) dev->send_poweroff_command();
rffc507x::spi::SPI().power_down();
portapack::shutdown(false, true);
// Unmount SD card and stop driver
f_mount(nullptr, reinterpret_cast<const TCHAR*>(_T("")), 0);
sdcDisconnect(&SDCD1);
sdcStop(&SDCD1);
// Signal application shutdown
ShutdownMessage shutdown_message;
shared_memory.application_queue.push(shutdown_message);
shared_memory.baseband_message = nullptr;
// Disable core interrupts and system tick
nvicDisableVector(DMA_IRQn);
nvicDisableVector(M4CORE_IRQn);
chSysDisable();
systick_stop();
SCB->ICSR |= SCB_ICSR_PENDSTCLR_Msk;
power_control::vaa_power_off();
power_control::core_power_off();
#ifdef PRALINE
// Power management and GPIO configuration for Praline hardware
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
LPC_GPIO->DIR[3] &= ~((1 << 7) | (1 << 5));
LPC_GPIO->DIR[5] &= ~(1 << 16);
#else
// Power management and GPIO configuration for legacy hardware
LPC_GPIO->DIR[0] &= ~0xFFFF4000;
LPC_GPIO->DIR[1] &= ~0xFFFF1000;
LPC_GPIO->DIR[2] &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 6) | (1 << 0));
LPC_GPIO->DIR[3] &= ~((1 << 7) | (1 << 5));
LPC_GPIO->DIR[5] &= ~(1 << 16);
#endif
// Power down peripherals (CGU cleanup)
LPC_RGU->RESET_CTRL[0] = (1 << 5); // USB0 Reset
LPC_CGU->PLL0USB_CTRL.PD = 1;
LPC_CGU->BASE_USB0_CLK.PD = 1;
LPC_CREG->CREG0 |= (1 << 5);
LPC_CGU->BASE_USB1_CLK.PD = 1;
LPC_CGU->BASE_UART0_CLK.PD = 1;
LPC_CGU->BASE_UART1_CLK.PD = 1;
LPC_CGU->BASE_UART2_CLK.PD = 1;
LPC_CGU->BASE_UART3_CLK.PD = 1;
LPC_CGU->BASE_SPI_CLK.PD = 1;
LPC_CGU->BASE_PERIPH_CLK.PD = 1;
LPC_CGU->BASE_SDIO_CLK.PD = 1;
LPC_CGU->BASE_SSP0_CLK.PD = 1;
LPC_CGU->BASE_SSP1_CLK.PD = 1;
LPC_CGU->BASE_LCD_CLK.PD = 1;
LPC_CGU->BASE_OUT_CLK.PD = 1;
(*(volatile uint32_t*)(&LPC_CGU->PLL0AUDIO_CTRL)) |= (1 << 0);
LPC_ADC0->CR &= ~(1 << 21);
LPC_ADC1->CR &= ~(1 << 21);
led_rx.setInactive();
led_usb.setInactive();
rtc_wakeup_init();
NVIC_EnableIRQ(I2C0_OR_I2C1_IRQn);
while (1) {
// --- Battery Status Check (I2C) ---
detect = battery::BatteryManagement::isDetected();
if (detect) {
bool dummy;
battery::BatteryManagement::getBatteryInfo(valid_mask, percent, voltage, current, dummy);
bool is_full = (valid_mask == 31 && percent == 100 && current <= 10) ||
(valid_mask == 1 && percent == 100);
if (is_full) {
// Case 1: Battery full (All LEDs off)
led_rx.setInactive();
led_tx.setInactive();
} else if ((voltage < 4150 && current < 10) || valid_mask == 0) {
// Case 2: Not full but low current draw (<10mA) -> Charging error
led_tx.setActive(); // LED indicates error/idle
led_rx.setInactive();
} else {
// Case 3: Actively charging
led_rx.setActive(); // LED indicates charging
led_tx.setInactive();
}
} else {
// Case 4: Battery IC not detected -> Error or H2 or older, so don't show that as an error.
led_tx.setActive();
led_rx.setActive();
}
// Shut down I2C and power down the APB bus for sleep
portapack::i2c0.stop();
LPC_CGU->BASE_APB1_CLK.PD = 1;
// Save interrupt states before mass disable
uint32_t saved_iser0 = NVIC->ISER[0];
// Disable and clear all pending interrupts
NVIC->ICER[0] = 0xFFFFFFFF;
NVIC->ICPR[0] = 0xFFFFFFFF;
// Re-enable only necessary wakeup sources
NVIC_EnableIRQ(RTC_IRQn);
NVIC_EnableIRQ(EVENTROUTER_IRQn);
// Configure RTC wakeup interval
if (valid_mask != 0 || detect) {
rtc_wakeup(60);
} else {
rtc_wakeup(3);
}
LPC_RTC->ILR = 3;
LPC_EVENTROUTER->CLR_STAT = 0xFFFFFFFF;
NVIC_ClearPendingIRQ(RTC_IRQn);
NVIC_ClearPendingIRQ(EVENTROUTER_IRQn);
__disable_irq();
// Configure and enter Deep Sleep
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__DSB();
__ISB();
// CPU enters sleep here
__WFI();
// --- WAKEUP SEQUENCE ---
__enable_irq();
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
// Cleanup RTC and restore peripheral clocks
LPC_RTC->AMR = 0xFF;
LPC_RTC->ILR = 3;
LPC_CGU->BASE_APB1_CLK.PD = 0;
LPC_RGU->RESET_CTRL[1] = (1 << 16);
// Short delay for power stability (3V3 rail)
for (volatile int d = 0; d < 10000; d++);
// Restart I2C controller
LPC_CGU->BASE_APB1_CLK.PD = 0;
portapack::i2c0.start(i2c_config_12mhz);
// Restore original interrupt enable states
NVIC->ISER[0] = saved_iser0;
} // End of while(1) deep sleep loop
} else {
portapack::display.wake(true);
}
}
eventmask_t EventDispatcher::wait() {
return chEvtWaitAny(ALL_EVENTS);
@@ -180,12 +371,10 @@ void EventDispatcher::dispatch(const eventmask_t events) {
/*if( events & EVT_MASK_LCD_FRAME_SYNC ) {
blink_timer();
}*/
if (events & EVT_MASK_LCD_FRAME_SYNC) {
handle_lcd_frame_sync(!EventDispatcher::display_sleep);
}
if (!EventDispatcher::display_sleep) {
if (events & EVT_MASK_LCD_FRAME_SYNC) {
handle_lcd_frame_sync();
}
if (events & EVT_MASK_ENCODER) {
handle_encoder();
}
@@ -211,8 +400,6 @@ void EventDispatcher::handle_local_queue() {
void EventDispatcher::handle_rtc_tick() {
sd_card::poll_inserted();
portapack::temperature_logger.second_tick();
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
if (backlight_timer.timeout_enabled()) {
if (portapack::bl_tick_counter == backlight_timer.timeout_seconds())
@@ -258,7 +445,9 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
if (!w->hidden()) {
// To achieve reverse depth ordering (last object drawn is
// considered "top"), descend first.
for (const auto child : w->children()) {
auto& children = w->children();
for (auto it = children.rbegin(); it != children.rend(); ++it) { // reverse, bc the lastly added will be "top" if overlaps
const auto& child = *it;
const auto touched_widget = touch_widget(child, event);
if (touched_widget) {
return touched_widget;
@@ -328,17 +517,16 @@ ui::Widget* EventDispatcher::getFocusedWidget() {
return context.focus_manager().focus_widget();
}
void EventDispatcher::handle_lcd_frame_sync() {
void EventDispatcher::handle_lcd_frame_sync(bool screen_on) {
bool waiting_for_frame = this->waiting_for_frame;
DisplayFrameSyncMessage message;
DisplayFrameSyncMessage message; // send framesync msg all the time, bc some apps relay on it
message_map.send(&message);
static_cast<ui::SystemView*>(top_widget)->paint_overlay();
painter.paint_widget_tree(top_widget);
portapack::backlight()->on();
if (screen_on) { // only draw when screen is on
static_cast<ui::SystemView*>(top_widget)->paint_overlay();
painter.paint_widget_tree(top_widget);
portapack::backlight()->on();
}
if (waiting_for_frame)
this->waiting_for_frame = false;
}
+3 -1
View File
@@ -63,6 +63,8 @@ class EventDispatcher {
static void set_display_sleep(const bool sleep);
static void charge_deep_sleep(const bool sleep);
static inline void check_fifo_isr() {
if (!shared_memory.application_queue.is_empty()) {
events_flag_isr(EVT_MASK_APPLICATION);
@@ -133,7 +135,7 @@ class EventDispatcher {
void on_keyboard_event(ui::KeyboardEvent event);
// void blink_timer();
void handle_lcd_frame_sync();
void handle_lcd_frame_sync(bool screen_on);
void handle_switches();
void handle_encoder();
void handle_touch();
+73 -4
View File
@@ -33,6 +33,76 @@ using namespace portapack;
namespace ui::external_app::acars_rx {
// ACARS frame field layout (0-based byte offsets):
// [0] SOH framing byte (skipped)
// [1..7] Aircraft registration (7 chars)
// [8] STX framing byte (skipped)
// [9..10] Label (2 chars)
// [11] Block ID (1 char)
// [12..14] Message number (3 chars)
// [15..20] Flight ID (6 chars)
// [21..N-3] Free-text payload (variable)
// [N-2..N-1] CRC-16/CCITT (2 bytes, MSB first, NOT part of payload)
//
// Minimum valid frame: 21-byte fixed header + 2 CRC bytes = 23 bytes.
// (A zero-length payload is legal per spec; we require at least 23 bytes.)
static constexpr std::string::size_type kAcarsCrcLen = 2;
static constexpr std::string::size_type kAcarsHeaderLen = 21;
static constexpr std::string::size_type kAcarsMinLen = kAcarsHeaderLen + kAcarsCrcLen;
// CRC-16/CCITT: poly 0x1021, init 0x0000, no reflection, no final XOR.
// This is the variant used by ACARS (same as XMODEM CRC).
static uint16_t acars_crc16(const std::string& data, std::string::size_type len) {
uint16_t crc = 0x0000;
for (std::string::size_type i = 0; i < len; ++i) {
crc ^= static_cast<uint16_t>(static_cast<uint8_t>(data[i])) << 8;
for (int bit = 0; bit < 8; ++bit) {
if (crc & 0x8000)
crc = static_cast<uint16_t>((crc << 1) ^ 0x1021);
else
crc = static_cast<uint16_t>(crc << 1);
}
}
return crc;
}
AcarsDecoded acars_decode(const std::string& raw) {
AcarsDecoded result;
if (raw.size() < kAcarsMinLen) {
result.txt = "ACARS message too short (" + std::to_string(raw.size()) +
" bytes, need " + std::to_string(kAcarsMinLen) + ")";
return result;
}
// Verify CRC: computed over everything except the last 2 bytes,
// then compared against those 2 bytes (MSB first).
const std::string::size_type payload_len = raw.size() - kAcarsCrcLen;
const uint16_t computed = acars_crc16(raw, payload_len);
const uint16_t received = (static_cast<uint16_t>(static_cast<uint8_t>(raw[raw.size() - 2])) << 8) |
static_cast<uint16_t>(static_cast<uint8_t>(raw[raw.size() - 1]));
result.crc_ok = (computed == received);
result.reg = raw.substr(1, 7);
result.label = raw.substr(9, 2);
result.block_id = raw[11];
result.msg_num = raw.substr(12, 3);
result.flight_id = raw.substr(15, 6);
// Payload sits between end of fixed header and the 2 trailing CRC bytes.
if (payload_len > kAcarsHeaderLen)
result.txt = raw.substr(kAcarsHeaderLen, payload_len - kAcarsHeaderLen);
return result;
}
std::string acars_format(const AcarsDecoded& msg) {
return std::string("ACARS Decoded Result\nCRC: ") + (msg.crc_ok ? "OK" : "FAIL") +
"\nRegistration: " + msg.reg +
"\nLabel: " + msg.label +
"\nBlockID: " + msg.block_id +
"\nMsgNum: " + msg.msg_num +
"\nFlightID: " + msg.flight_id +
"\nMessage: " + msg.txt;
}
void ACARSLogger::log_str(std::string msg) {
log_file.write_entry(msg);
}
@@ -77,17 +147,16 @@ void ACARSAppView::focus() {
void ACARSAppView::on_packet(const ACARSPacketMessage* packet) {
std::string console_info;
if (packet->state == 255) {
// got a packet, parse it, and display
rtc::RTC datetime;
rtc_time::now(datetime);
// todo parity error recovery
console_info = to_string_datetime(datetime, HMS);
console_info += ": ";
console_info += packet->message;
std::string message{packet->message, packet->message + packet->msg_len};
AcarsDecoded decoded = acars_decode(message);
console_info += acars_format(decoded);
console.writeln(console_info);
// Log raw data whatever it contains
if (logger && logging)
logger->log_str(console_info);
} else {

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