Compare commits

...

52 Commits

Author SHA1 Message Date
Totoo 8c996b5bc6 Embedded friendly usb (#1817) 2024-01-27 19:33:08 +01:00
Mark Thompson d1901939e1 Fix tuning of 2170-2300 and 2700-2740 MHz (#1819) 2024-01-27 13:30:32 +01:00
Mark Thompson 41ec11fb66 Check app checksums during untar (#1815) 2024-01-27 13:29:24 +01:00
Mark Thompson df47490d85 Allow 3 boot failures before Config Mode starts (#1816)
* Allow 3 boot attempts before Config Mode starts

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

* Test code to verify firmware checksum

* Comment out unneeded zlib

* Add files via upload

* Print space remaining in ROM

* Append checksum to external apps too

* Check external app checksums when loading

* Is it 2024 already?!

* Validate firmware checksum before flashing

* Add files via upload

* Added flash error warning to nav screen

* Clang

* Replaced some hard-coded values with #defines

* Check FW checksum before USB serial flash too

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

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

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

* Attempt 2 after reading gh docs :D

* Fix changelog for stable release too
2024-01-22 10:50:14 +01:00
Mark Thompson 0f85f247b6 Fix rounding bug when displaying frequencies (#1801) 2024-01-22 07:54:29 +01:00
sommermoregentraum ac1d350aaf add a item that warn user to put ext app if nothing to pop in home page (#1798)
add an item that warn user to put ext app if nothing to pop
2024-01-22 03:21:06 +08:00
Mark Thompson 5f8e1ef307 Daylight Savings Time support (#1793)
* Daylight Savings Time support

* Cleanup

* Clean-up

* Revert ADSB change

* Clean-up

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

* format

* cleanup

* format

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

* Only include today's DATE in filename

* Remove date from PPFW filename

* Allow any .tar file name in FIRMWARE folder

* Add files via upload

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

* using fillObuffer instead of chprintf

---------

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

* fixed usb input buffer handling

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

* Removed travis

* Removed travis

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

* faster file over usbcdc - wip

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

* improved file system commands

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

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

* Update create_nightly_release.yml

Updated zip for work map

* Update create_stable_release.yml

Replicated on stable release
2024-01-07 19:51:27 +13:00
101 changed files with 3042 additions and 789 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ assignees: ''
----
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/eried/portapack-mayhem/releases)
(Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases)
----
@@ -9,8 +9,8 @@ assignees: ''
----
Before creating this issue, **do the following**:
* Read the Wiki on booting: https://github.com/eried/portapack-mayhem/wiki/Won't-boot
* Read: https://github.com/eried/portapack-havoc/wiki/Update-firmware
* Read the Wiki on booting: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
* Read: https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
* Watch carefully: https://www.youtube.com/watch?v=_zx4ZvurgOs
* (if you are not in Windows) also check: https://www.youtube.com/watch?v=kjFB58Y1TAo
@@ -27,12 +27,12 @@ Steps to reproduce the behavior:
**My Hardware**
Please specify what PortaPack hardware version you are using.
You can find the list of versions here: https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions
You can find the list of versions here: https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions
**Affected versions**
Please tell us what version you are running.
If your device is still functional, try the latest nightly release before submitting this.
You can find the latest nightly version here https://github.com/eried/portapack-mayhem/releases
You can find the latest nightly version here https://github.com/portapack-mayhem/mayhem-firmware/releases
**Were you able to update the firmware before?**
Things might be confusing the first time, please check the video available on the link above.
+2 -2
View File
@@ -6,8 +6,8 @@ from datetime import datetime, timedelta, timezone
# Set up your personal access token and the repository details
token = os.environ.get('GH_TOKEN')
repo_owner = "eried"
repo_name = "portapack-mayhem"
repo_owner = "portapack-mayhem"
repo_name = "mayhem-firmware"
def print_stable_changelog(previous_sha):
+12 -12
View File
@@ -20,7 +20,7 @@ jobs:
- name: check latest commit is less than a day
id: should_run
continue-on-error: true
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
build:
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
@@ -28,10 +28,10 @@ jobs:
steps:
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Get version date
id: version_date
run: echo "::set-output name=date::n_$(date +'%y%m%d')"
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@master
with:
@@ -54,10 +54,10 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
@@ -66,16 +66,16 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py)
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
echo "::set-output name=content::$CHANGELOG"
{
echo 'content<<EOF'
python3 .github/workflows/changelog.py
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
- name: Create Release
id: create_release
@@ -100,7 +100,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/firmware/portapack-mayhem.ppfw.tar
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
+15 -15
View File
@@ -9,7 +9,7 @@ jobs:
steps:
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@master
with:
@@ -26,10 +26,10 @@ jobs:
git submodule update --init --recursive
- name: Get version
id: version
run: echo "::set-output name=version::$(cat .github/workflows/version.txt)"
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
- name: Get past version
id: past_version
run: echo "::set-output name=past_version::$(cat .github/workflows/past_version.txt)"
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
- name: Build the Docker image
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
- name: Make build folder
@@ -38,10 +38,10 @@ jobs:
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
- name: Create Small SD Card ZIP - No World Map
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
- name: Download world map
run: |
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
- name: Unzip world map
run: |
unzip world_map.zip -d sdcard/ADSB
@@ -50,16 +50,16 @@ jobs:
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
- name: Create SD Card ZIP
run: |
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
CHANGELOG="${CHANGELOG//'%'/'%25'}"
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
echo "::set-output name=content::$CHANGELOG"
{
echo 'content<<EOF'
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
echo EOF
} >> "$GITHUB_OUTPUT"
id: changelog
- name: Create Release
id: create_release
@@ -71,15 +71,15 @@ jobs:
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
body: |
**Stable release - ${{ steps.version.outputs.version }}**
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/eried/portapack-mayhem/blob/master/README.md) for details.
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/portapack-mayhem/mayhem-firmware/blob/master/README.md) for details.
## Release notes
### Revision (${{ steps.version.outputs.version }}):
${{ steps.changelog.outputs.content }}
**Full Changelog**: https://github.com/eried/portapack-mayhem/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
**Full Changelog**: https://github.com/portapack-mayhem/mayhem-firmware/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
## Installation
Check the [wiki](https://github.com/eried/portapack-havoc/wiki/Update-firmware) for details how to upgrade.
Check the [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware) for details how to upgrade.
__Warning:__ Since release 1.8.0, some applications has been moved to the SD card as we ran out of flash space.
### MicroSD card files
@@ -94,7 +94,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/firmware/portapack-mayhem.ppfw.tar
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
-11
View File
@@ -46,7 +46,6 @@ if ("${VERSION}" STREQUAL "")
set(VERSION_NOHASH "dev")
else()
set(VERSION_NOHASH "${VERSION}")
set(PPFW_FILENAME "portapack-mayhem.ppfw.tar")
endif()
execute_process(
@@ -55,16 +54,6 @@ execute_process(
)
set(VERSION_MD5 "0x${VERSION_MD5}")
if (NOT DEFINED PPFW_FILENAME)
# Funny business here: trying to generate unique names to prevent renaming of shared test-drive builds by the browser on download to .ppfw-42.tar as it wont be recognized by the flasher
execute_process(
COMMAND date "+%y%m%d-%H%M"
OUTPUT_VARIABLE PPFW_FILENAME
)
string(STRIP ${PPFW_FILENAME} PPFW_FILENAME)
set(PPFW_FILENAME "portapack-mayhem_${PPFW_FILENAME}_OCI.ppfw.tar")
endif()
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
+14 -14
View File
@@ -1,15 +1,15 @@
> [!IMPORTANT]
> *Shortly*, this repository **will be moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem/portapack-mayhem](https://github.com/portapack-mayhem/portapack-mayhem). Please keep this in mind to **update your links** accordingly.
> this repository **has just been moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware). Please keep this in mind to **update your links** accordingly.
# PortaPack Mayhem
[![Build Status](https://travis-ci.com/eried/portapack-mayhem.svg?branch=master)](https://travis-ci.com/eried/portapack-mayhem) [![Nightly Release](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/eried/portapack-mayhem/total)](https://github.com/eried/portapack-mayhem/releases) [![GitHub Releases](https://img.shields.io/github/downloads/eried/portapack-mayhem/latest/total)](https://github.com/eried/portapack-mayhem/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](https://discord.gg/tuwVMv3)
[![Nightly Release](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases) [![GitHub Releases](https://img.shields.io/github/downloads/portapack-mayhem/mayhem-firmware/latest/total)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](https://discord.gg/tuwVMv3)
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
[<img src="https://raw.githubusercontent.com/wiki/eried/portapack-mayhem/img/hw_overview_h2_front.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/eried/portapack-mayhem/img/hw_overview_h2_inside.png" height="400">](https://github.com/eried/portapack-mayhem/wiki/Hardware-overview#portapack-internals)
[<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_front.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview) [<img src="https://raw.githubusercontent.com/wiki/portapack-mayhem/mayhem-firmware/img/hw_overview_h2_inside.png" height="400">](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview#portapack-internals)
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure)*
*[PortaPack H2+HackRF+battery](https://s.click.aliexpress.com/e/_DmU7GQX) (clone) with a custom [3d printed case](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure)*
# What is this?
@@ -21,11 +21,11 @@ For people familiar with the [Flipper Zero](https://github.com/flipperdevices/fl
# Frequently Asked Questions
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) is always welcomed and appreciated.
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) is always welcomed and appreciated.
## What to buy?
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), that includes everything you need with the plastic case "inspired" on [this](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/H2-Enclosure).
:heavy_check_mark: Our friends at OpenSourceSDRLab give away five units every three months in our discord (check the badge on top) of their [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too by ordering.
@@ -35,33 +35,33 @@ This repository expands upon the previous work by many people and aims to consta
![image](https://user-images.githubusercontent.com/1091420/214579017-9ad970b9-0917-48f6-a550-588226d3f89b.png)
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/eried/portapack-mayhem/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
:warning: If it looks **too different**, this might mean that they are using their own recipe, check the [different models](https://github.com/portapack-mayhem/mayhem-firmware/wiki/PortaPack-Versions) in our wiki. For example all the H3 and clones of that version use their own version of the firmware. They do not contribute the changes back and eventually you will be left with a device that nobody maintains:
![image](https://user-images.githubusercontent.com/1091420/214581333-424900ee-26f8-4e96-be2f-69d8dc995ba9.png)
## Where is the latest version?
The current **stable release** is on the [![GitHub release (latest by date)](https://img.shields.io/github/v/release/eried/portapack-mayhem?label=Releases&style=social)](https://github.com/eried/portapack-mayhem/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/eried/portapack-mayhem/releases/).
The current **stable release** is on the [![GitHub release (latest by date)](https://img.shields.io/github/v/release/portapack-mayhem/mayhem-firmware?label=Releases&style=social)](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) page. Follow the instructions you can find in the release description. The **latest (nightly) release** can be found [here](https://github.com/portapack-mayhem/mayhem-firmware/releases/).
## How can I collaborate
You can write [documentation](https://github.com/eried/portapack-mayhem/wiki), fix bugs and [answer issues](https://github.com/eried/portapack-mayhem/issues) or add new functionality. Please check the following [guide](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) with details.
You can write [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki), fix bugs and [answer issues](https://github.com/portapack-mayhem/mayhem-firmware/issues) or add new functionality. Please check the following [guide](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) with details.
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/eried/portapack-mayhem/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
Consider that the hardware and firmware has been created and maintain by a [lot](https://github.com/mossmann/hackrf/graphs/contributors) of [people](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors), so always try collaborating your time and effort first. For coding related questions, if something does not fit as an issue, please join our Discord by clicking the chat badge on [top](#portapack-mayhem).
[![Contributors](https://contrib.rocks/image?repo=eried/portapack-mayhem)](https://github.com/eried/portapack-mayhem/graphs/contributors)
[![Contributors](https://contrib.rocks/image?repo=portapack-mayhem/mayhem-firmware)](https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors)
To support the people behind the hardware, please buy a genuine [HackRF](https://greatscottgadgets.com/hackrf/) and [PortaPack](https://store.sharebrained.com/products/portapack-for-hackrf-one-kit).
## What if I really want something specific?
If what you need can be relevant in general, you can [request a feature](https://github.com/eried/portapack-mayhem/issues/new?labels=enhancement&template=feature_request.md).
If what you need can be relevant in general, you can [request a feature](https://github.com/portapack-mayhem/mayhem-firmware/issues/new?labels=enhancement&template=feature_request.md).
<del>You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/eried/portapack-mayhem/issues/new/choose) or [choosing](https://github.com/eried/portapack-mayhem/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).</del>
<del>You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose) or [choosing](https://github.com/portapack-mayhem/mayhem-firmware/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).</del>
<del>Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).</del>
Bountysource has not been reliable lately, so until this changes, please **DO NOT** post a bounty there. Go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
## What if I need help?
First, check the [documentation](https://github.com/eried/portapack-mayhem/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/eried/portapack-mayhem/issues/new/choose).
First, check the [documentation](https://github.com/portapack-mayhem/mayhem-firmware/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/portapack-mayhem/mayhem-firmware/issues/new/choose).
You can reach the [official community](https://www.facebook.com/groups/177623356165819) in Facebook, and our Discord by clicking the chat badge on [top](#portapack-mayhem).
+2 -2
View File
@@ -1,6 +1,6 @@
<head>
<meta http-equiv="refresh" content="0; URL=https://github.com/eried/portapack-mayhem/" />
<meta http-equiv="refresh" content="0; URL=https://github.com/portapack-mayhem/mayhem-firmware/" />
</head>
<body>
<p>If you are not redirected, <a href="https://github.com/eried/portapack-mayhem/">click here</a>.</p>
<p>If you are not redirected, <a href="https://github.com/portapack-mayhem/mayhem-firmware/">click here</a>.</p>
</body>
+2 -1
View File
@@ -35,6 +35,7 @@ set(LZ4 lz4)
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
# In our current build container cmake need a little help to get the version :)
if(NOT DEFINED ${CMAKE_CXX_COMPILER_VERSION})
@@ -100,7 +101,7 @@ add_custom_command(
COMMAND mkdir -p firmware_tar/APPS
COMMAND cp application/*.ppma firmware_tar/APPS
COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
DEPENDS firmware
DEPENDS firmware ${FIRMWARE_FILENAME}
# Dont use VERBATIM here as it prevents usage of globbing (*)
# There shouldnt be any funny business in the filenames above :)
)
+4 -1
View File
@@ -116,7 +116,7 @@ set(CSRC
usb_serial_cdc.c
usb_serial_descriptor.c
usb_serial_endpoints.c
usb_serial_io.c
usb_serial_device_to_host.c
${HACKRF_PATH}/firmware/common/usb.c
${HACKRF_PATH}/firmware/common/usb_queue.c
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
@@ -203,9 +203,11 @@ set(CPPSRC
metadata_file.cpp
portapack.cpp
usb_serial_shell.cpp
usb_serial_shell_filesystem.cpp
usb_serial_event.cpp
usb_serial_thread.cpp
usb_serial.cpp
usb_serial_host_to_device.cpp
qrcodegen.cpp
radio.cpp
receiver_model.cpp
@@ -344,6 +346,7 @@ set(CPPSRC
${CPLD_20170522_DATA_CPP}
${HACKRF_CPLD_DATA_CPP}
ui_external_items_menu_loader.cpp
view_factory_base.cpp
${EXTCPPSRC}
)
+2 -2
View File
@@ -40,7 +40,7 @@ using namespace portapack;
namespace {
fs::path get_settings_path(const std::string& app_name) {
return fs::path{u"/SETTINGS"} / app_name + u".ini";
return fs::path{SETTINGS_DIR} / app_name + u".ini";
}
} // namespace
@@ -156,7 +156,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
File f;
auto path = get_settings_path(std::string{store_name});
make_new_directory(SETTINGS_DIR);
ensure_directory(SETTINGS_DIR);
auto error = f.create(path);
if (error)
return false;
+22 -3
View File
@@ -26,6 +26,7 @@
#include "string_format.hpp"
#include "tonesets.hpp"
#include "ui_tone_key.hpp"
#include "audio.hpp"
using namespace tonekey;
using namespace portapack;
@@ -40,6 +41,7 @@ void SoundBoardView::stop() {
if (is_active())
replay_thread.reset();
audio::output::stop();
transmitter_model.disable();
tx_view.set_transmitting(false);
@@ -78,7 +80,13 @@ void SoundBoardView::file_error() {
}
void SoundBoardView::start_tx(const uint32_t id) {
if (file_list.empty()) {
file_error();
return;
}
auto reader = std::make_unique<WAVFileReader>();
uint32_t tone_key_index = options_tone_key.selected_index();
uint32_t sample_rate;
@@ -108,11 +116,11 @@ void SoundBoardView::start_tx(const uint32_t id) {
// TODO: Delete all this and use tx model.
baseband::set_audiotx_config(
1536000 / 20, // Update vu-meter at 20Hz
TONES_SAMPLERATE / 20, // Update vu-meter at 20Hz
transmitter_model.channel_bandwidth(),
0, // Gain is unused
8, // shift_bits_s16, default 8 bits, but also unused
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
0, // AM
0, // DSB
0, // USB
@@ -123,6 +131,9 @@ void SoundBoardView::start_tx(const uint32_t id) {
transmitter_model.enable();
tx_view.set_transmitting(true);
if (tone_key_index == 0)
audio::output::start();
}
/*void SoundBoardView::show_infos() {
@@ -227,6 +238,8 @@ SoundBoardView::SoundBoardView(
//&text_title,
//&text_duration,
//&progressbar,
&field_volume,
&text_volume_disabled,
&page_info,
&check_loop,
&check_random,
@@ -252,6 +265,13 @@ SoundBoardView::SoundBoardView(
tone_keys_populate(options_tone_key);
options_tone_key.set_selected_index(0);
text_volume_disabled.hidden(true);
options_tone_key.on_change = [this](size_t index, OptionsField::value_t) {
bool tone_key_enabled = (index != 0);
text_volume_disabled.hidden(!tone_key_enabled);
field_volume.hidden(tone_key_enabled);
};
check_loop.set_value(false);
check_random.set_value(false);
@@ -274,7 +294,6 @@ SoundBoardView::SoundBoardView(
SoundBoardView::~SoundBoardView() {
stop();
transmitter_model.disable();
baseband::shutdown();
}
+8 -2
View File
@@ -91,7 +91,7 @@ class SoundBoardView : public View {
void on_select_entry();
Labels labels{
//{ { 0, 20 * 8 + 4 }, "Title:", Color::light_grey() },
{{24 * 8, 180}, "Vol:", Color::light_grey()},
{{0, 180}, "Key:", Color::light_grey()}};
Button button_next_page{
@@ -122,10 +122,16 @@ class SoundBoardView : public View {
};*/
OptionsField options_tone_key{
{32, 180},
{4 * 8, 180},
18,
{}};
AudioVolumeField field_volume{
{28 * 8, 180}};
Text text_volume_disabled{
{28 * 8, 180, 3 * 8, 16},
"--"};
Checkbox check_loop{
{0, 25 * 8 + 4},
4,
@@ -19,7 +19,7 @@ void AboutView::update() {
switch (++frame) {
case 1:
// TODO: Generate this automatically from github
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2022-01-01&from=2020-04-12&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Mayhem:");
console.writeln("eried,euquiq,gregoryfenton");
console.writeln("johnelder,jwetzell,nnemanjan00");
@@ -42,7 +42,7 @@ void AboutView::update() {
break;
case 3:
// https://github.com/eried/portapack-mayhem/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?to=2020-04-12&from=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "Havoc:");
console.writeln("furrtek,mrmookie,NotPike");
console.writeln("mjwaxios,ImDroided,Giorgiofox");
@@ -55,7 +55,7 @@ void AboutView::update() {
break;
case 4:
// https://github.com/eried/portapack-mayhem/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
// https://github.com/portapack-mayhem/mayhem-firmware/graphs/contributors?from=2014-07-05&to=2015-07-31&type=c
console.writeln(STR_COLOR_DARK_YELLOW "PortaPack:");
console.writeln("jboone,argilo");
console.writeln("");
+12 -1
View File
@@ -318,6 +318,17 @@ bool ADSBRxDetailsView::add_map_marker(const AircraftRecentEntry& entry) {
return markerStored == MARKER_STORED;
}
void ADSBRxDetailsView::on_gps(const GPSPosDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
}
void ADSBRxDetailsView::on_orientation(const OrientationDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_orientation(msg->angle);
}
void ADSBRxDetailsView::refresh_ui() {
auto age = entry_.age;
if (age < 60)
@@ -396,7 +407,7 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
status_good_frame.toggle();
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
rtcGetTime(&RTCD1, &datetime); // Reading RTC directly to avoid DST transitions when calculating delta
frame.set_rx_timestamp(datetime.minute() * 60 + datetime.second());
// NB: Reference to update entry in-place.
+15
View File
@@ -271,6 +271,8 @@ class ADSBRxDetailsView : public View {
private:
void refresh_ui();
void on_gps(const GPSPosDataMessage* msg);
void on_orientation(const OrientationDataMessage* msg);
GeoMapView* geomap_view_{nullptr};
ADSBRxAircraftDetailsView* aircraft_details_view_{nullptr};
@@ -330,6 +332,19 @@ class ADSBRxDetailsView : public View {
Button button_see_map{
{16 * 8, 9 * 16, 12 * 8, 3 * 16},
"See on map"};
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
[this](Message* const p) {
const auto message = static_cast<const GPSPosDataMessage*>(p);
this->on_gps(message);
}};
MessageHandlerRegistration message_handler_orientation{
Message::ID::OrientationData,
[this](Message* const p) {
const auto message = static_cast<const OrientationDataMessage*>(p);
this->on_orientation(message);
}};
};
/* Main ADSB application view and message dispatch. */
+3 -1
View File
@@ -101,6 +101,8 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
field_frequency.set_value(145175000);
} else if (i == 3) {
field_frequency.set_value(144575000);
} else if (i == 4) {
field_frequency.set_value(145825000);
}
};
@@ -258,7 +260,7 @@ void APRSTableView::on_pkt(const APRSPacketMessage* message) {
std::string source_formatted = packet.get_source_formatted();
std::string info_string = packet.get_stream_text();
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
auto& entry = ::on_packet(recent, packet.get_source());
entry.reset_age();
entry.inc_hit();
+2 -1
View File
@@ -221,7 +221,8 @@ class APRSRxView : public View {
{{"NA ", 0},
{"EUR", 1},
{"AUS", 2},
{"NZ ", 3}}};
{"NZ ", 3},
{"ISS", 4}}};
RxFrequencyField field_frequency{
{3 * 8, 0 * 16},
+12 -10
View File
@@ -48,7 +48,7 @@ void DfuMenu::paint(Painter& painter) {
text_info_line_3.set(to_string_dec_uint(utilisation, 6));
text_info_line_4.set(to_string_dec_uint(shared_memory.m4_heap_usage, 6));
text_info_line_5.set(to_string_dec_uint(shared_memory.m4_stack_usage, 6));
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_cpu_usage, 6));
text_info_line_6.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
text_info_line_7.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
text_info_line_8.set(to_string_dec_uint(chTimeNow() / 1000, 6));
@@ -94,23 +94,25 @@ DfuMenu2::DfuMenu2(NavigationView& nav)
&text_info_line_7,
&text_info_line_8,
&text_info_line_9,
&text_info_line_10});
&text_info_line_10,
&text_info_line_11});
}
void DfuMenu2::paint(Painter& painter) {
text_info_line_1.set(to_string_dec_uint(portapack::receiver_model.target_frequency(), 10));
text_info_line_2.set(to_string_dec_uint(portapack::receiver_model.baseband_bandwidth(), 10));
text_info_line_3.set(to_string_dec_uint(portapack::receiver_model.sampling_rate(), 10));
text_info_line_4.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
text_info_line_5.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
text_info_line_8.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
text_info_line_4.set(to_string_dec_uint(((uint32_t)shared_memory.m4_performance_counter) * 100 / 127, 10));
text_info_line_5.set(to_string_dec_uint((uint32_t)portapack::receiver_model.modulation(), 10));
text_info_line_6.set(to_string_dec_uint(portapack::receiver_model.am_configuration(), 10));
text_info_line_7.set(to_string_dec_uint(portapack::receiver_model.nbfm_configuration(), 10));
text_info_line_8.set(to_string_dec_uint(portapack::receiver_model.wfm_configuration(), 10));
text_info_line_9.set(to_string_dec_uint(portapack::transmitter_model.target_frequency(), 10));
text_info_line_10.set(to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth(), 10));
text_info_line_11.set(to_string_dec_uint(portapack::transmitter_model.sampling_rate(), 10));
constexpr auto margin = 5;
constexpr auto lines = 10 + 2;
constexpr auto lines = 11 + 2;
painter.fill_rectangle(
{{5 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
+10 -7
View File
@@ -83,13 +83,15 @@ class DfuMenu2 : public View {
{{5 * CHARACTER_WIDTH, 5 * LINE_HEIGHT}, "RX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 6 * LINE_HEIGHT}, "RX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 7 * LINE_HEIGHT}, "RX SampR:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()}};
{{5 * CHARACTER_WIDTH, 8 * LINE_HEIGHT}, "RX Satu%:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 9 * LINE_HEIGHT}, "Modulatn:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 10 * LINE_HEIGHT}, "AM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "NBFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "WFM cfg:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "TX Freq:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "TX BW:", Color::dark_cyan()},
{{5 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "TX SampR:", Color::dark_cyan()},
};
Text text_info_line_1{{14 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_2{{14 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
@@ -101,6 +103,7 @@ class DfuMenu2 : public View {
Text text_info_line_8{{14 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_9{{14 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_10{{14 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
Text text_info_line_11{{14 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 10 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
};
} /* namespace ui */
+36 -9
View File
@@ -21,6 +21,7 @@
*/
#include "ui_flash_utility.hpp"
#include "ui_styles.hpp"
#include "portapack_shared_memory.hpp"
namespace ui {
@@ -30,6 +31,30 @@ static const char16_t* firmware_folder = u"/FIRMWARE";
Thread* FlashUtilityView::thread{nullptr};
static constexpr size_t max_filename_length = 26;
bool valid_firmware_file(std::filesystem::path::string_type path) {
File firmware_file;
uint32_t read_buffer[128];
uint32_t checksum{(uint32_t)~FLASH_EXPECTED_CHECKSUM}; // initializing to invalid checksum in case file can't be read
// test read of the whole file just to validate checksum (baseband flash code will re-read when flashing)
auto result = firmware_file.open(path.c_str());
if (!result.is_valid()) {
checksum = 0;
for (uint32_t i = 0; i < FLASH_ROM_SIZE / sizeof(read_buffer); i++) {
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
// if file is smaller than 1MB, assume it's a downgrade to an old FW version and ignore the checksum
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
checksum = FLASH_EXPECTED_CHECKSUM;
break;
}
checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer));
}
}
return (checksum == FLASH_EXPECTED_CHECKSUM);
}
FlashUtilityView::FlashUtilityView(NavigationView& nav)
: nav_(nav) {
add_children({&labels,
@@ -50,7 +75,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
this->firmware_selected(path);
}});
}
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.ppfw.tar")) {
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.tar")) {
auto filename = entry.path().filename();
auto path = entry.path().native();
@@ -98,21 +123,23 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
painter.draw_string({0, 60}, this->nav_.style(), fileName);
});
if (res.string().empty()) {
ui::Painter painter;
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
painter.draw_string({0, 60}, this->nav_.style(), "BAD TAR FILE");
chThdSleepMilliseconds(5000);
}
return res;
}
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
if (endsWith(path, u".ppfw.tar")) {
if (endsWith(path, u".tar")) {
// extract, then update
path = extract_tar(u'/' + path).native();
if (path.empty()) return;
}
if (path.empty() || !valid_firmware_file(path.c_str())) {
ui::Painter painter;
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
painter.draw_string({0, 60}, Styles::red, "BAD FIRMWARE FILE");
chThdSleepMilliseconds(5000);
return;
}
ui::Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
@@ -32,8 +32,14 @@
#include "untar.hpp"
#include <cstdint>
#define FLASH_ROM_SIZE 1048576
#define FLASH_STARTING_ADDRESS 0x00000000
#define FLASH_EXPECTED_CHECKSUM 0x00000000
namespace ui {
bool valid_firmware_file(std::filesystem::path::string_type path);
class FlashUtilityView : public View {
public:
FlashUtilityView(NavigationView& nav);
+2 -11
View File
@@ -277,18 +277,9 @@ NumbersStationView::NumbersStationView(
symfield_code.set_offset(10, 12); // End
/*
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
// Thanks, Sakamoto-sama !
y = datetime.year();
m = datetime.month();
d = datetime.day();
y -= m < 3;
dayofweek = (y + y/4 - y/100 + y/400 + month_table[m-1] + d) % 7;
dayofweek = rtc_time::current_day_of_week();
text_title.set(day_of_week[dayofweek]);
*/
*/
button_exit.on_select = [&nav](Button&) {
nav.pop();
-2
View File
@@ -54,8 +54,6 @@
namespace ui {
#define RECON_CFG_FILE u"SETTINGS/recon.cfg"
enum class recon_mode : uint8_t {
Recon,
Scanner,
+1 -1
View File
@@ -275,7 +275,7 @@ ScannerView::~ScannerView() {
}
void ScannerView::show_max_index() { // show total number of freqs to scan
field_current_index.set_text("---");
field_current_index.set_text("<->");
if (entries.size() == FREQMAN_MAX_PER_FILE) {
text_max_index.set_style(&Styles::red);
+110 -10
View File
@@ -27,6 +27,7 @@
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_touch_calibration.hpp"
#include "ui_text_editor.hpp"
#include "portapack_persistent_memory.hpp"
#include "lpc43xx_cpp.hpp"
@@ -42,6 +43,7 @@ namespace fs = std::filesystem;
#include "string_format.hpp"
#include "ui_styles.hpp"
#include "cpld_update.hpp"
#include "config_mode.hpp"
namespace pmem = portapack::persistent_memory;
@@ -59,10 +61,9 @@ SetDateTimeView::SetDateTimeView(
NavigationView& nav) {
button_save.on_select = [&nav, this](Button&) {
const auto model = this->form_collect();
const rtc::RTC new_datetime{
model.year, model.month, model.day,
model.hour, model.minute, model.second};
rtcSetTime(&RTCD1, &new_datetime);
rtc::RTC new_datetime{model.year, model.month, model.day, model.hour, model.minute, model.second};
pmem::set_config_dst(model.dst);
rtc_time::set(new_datetime); // NB: 1 hour will be subtracted if value is stored in RTC during DST
nav.pop();
},
@@ -78,20 +79,49 @@ SetDateTimeView::SetDateTimeView(
&field_hour,
&field_minute,
&field_second,
&text_weekday,
&text_day_of_year,
&checkbox_dst_enable,
&options_dst_start_which,
&options_dst_start_weekday,
&options_dst_start_month,
&options_dst_end_which,
&options_dst_end_weekday,
&options_dst_end_month,
&button_save,
&button_cancel,
});
// Populate DST options (same string text for start & end)
options_dst_start_which.set_options(which_options);
options_dst_end_which.set_options(which_options);
options_dst_start_weekday.set_options(weekday_options);
options_dst_end_weekday.set_options(weekday_options);
options_dst_start_month.set_options(month_options);
options_dst_end_month.set_options(month_options);
const auto date_changed_fn = [this](int32_t) {
auto weekday = rtc_time::day_of_week(field_year.value(), field_month.value(), field_day.value());
auto doy = rtc_time::day_of_year(field_year.value(), field_month.value(), field_day.value());
bool valid_date = (field_day.value() <= rtc_time::days_per_month(field_year.value(), field_month.value()));
text_weekday.set(valid_date ? weekday_options[weekday].first : "-");
text_day_of_year.set(valid_date ? to_string_dec_uint(doy, 3) : "-");
};
field_year.on_change = date_changed_fn;
field_month.on_change = date_changed_fn;
field_day.on_change = date_changed_fn;
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
SetDateTimeModel model{
datetime.year(),
datetime.month(),
datetime.day(),
datetime.hour(),
datetime.minute(),
datetime.second()};
datetime.second(),
pmem::config_dst()};
form_init(model);
}
@@ -106,16 +136,32 @@ void SetDateTimeView::form_init(const SetDateTimeModel& model) {
field_hour.set_value(model.hour);
field_minute.set_value(model.minute);
field_second.set_value(model.second);
checkbox_dst_enable.set_value(model.dst.b.dst_enabled);
options_dst_start_which.set_by_value(model.dst.b.start_which);
options_dst_start_weekday.set_by_value(model.dst.b.start_weekday);
options_dst_start_month.set_by_value(model.dst.b.start_month);
options_dst_end_which.set_by_value(model.dst.b.end_which);
options_dst_end_weekday.set_by_value(model.dst.b.end_weekday);
options_dst_end_month.set_by_value(model.dst.b.end_month);
}
SetDateTimeModel SetDateTimeView::form_collect() {
pmem::dst_config_t dst;
dst.b.dst_enabled = static_cast<uint8_t>(checkbox_dst_enable.value());
dst.b.start_which = static_cast<uint8_t>(options_dst_start_which.selected_index_value());
dst.b.start_weekday = static_cast<uint8_t>(options_dst_start_weekday.selected_index_value());
dst.b.start_month = static_cast<uint8_t>(options_dst_start_month.selected_index_value());
dst.b.end_which = static_cast<uint8_t>(options_dst_end_which.selected_index_value());
dst.b.end_weekday = static_cast<uint8_t>(options_dst_end_weekday.selected_index_value());
dst.b.end_month = static_cast<uint8_t>(options_dst_end_month.selected_index_value());
return {
.year = static_cast<uint16_t>(field_year.value()),
.month = static_cast<uint8_t>(field_month.value()),
.day = static_cast<uint8_t>(field_day.value()),
.hour = static_cast<uint8_t>(field_hour.value()),
.minute = static_cast<uint8_t>(field_minute.value()),
.second = static_cast<uint8_t>(field_second.value())};
.second = static_cast<uint8_t>(field_second.value()),
.dst = dst};
}
/* SetRadioView ******************************************/
@@ -628,6 +674,58 @@ void SetEncoderDialView::focus() {
button_save.focus();
}
/* AppSettingsView ************************************/
AppSettingsView::AppSettingsView(
NavigationView& nav)
: nav_{nav} {
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
ensure_directory(SETTINGS_DIR);
for (const auto& entry : std::filesystem::directory_iterator(SETTINGS_DIR, u"*.ini")) {
auto path = (std::filesystem::path)SETTINGS_DIR / entry.path();
menu_view.add_item({path.filename().string().substr(0, 26),
ui::Color::dark_cyan(),
&bitmap_icon_file_text,
[this, path](KeyEvent) {
nav_.push<TextEditorView>(path);
}});
}
}
void AppSettingsView::focus() {
menu_view.focus();
}
/* SetConfigModeView ************************************/
SetConfigModeView::SetConfigModeView(NavigationView& nav) {
add_children({&labels,
&checkbox_config_mode_enabled,
&button_save,
&button_cancel});
checkbox_config_mode_enabled.set_value(!pmem::config_disable_config_mode());
button_save.on_select = [&nav, this](Button&) {
pmem::set_config_disable_config_mode(!checkbox_config_mode_enabled.value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetConfigModeView::focus() {
button_save.focus();
}
/* SettingsMenuView **************************************/
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
@@ -635,17 +733,19 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<AppSettingsView>(); }},
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [&nav]() { nav.push<SetConfigModeView>(); }},
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetRadioView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SetSDCardView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
});
set_max_rows(2); // allow wider buttons
}
+111 -8
View File
@@ -44,6 +44,7 @@ struct SetDateTimeModel {
uint8_t hour;
uint8_t minute;
uint8_t second;
portapack::persistent_memory::dst_config_t dst;
};
class SetDateTimeView : public View {
@@ -55,56 +56,111 @@ class SetDateTimeView : public View {
std::string title() const override { return "Date/Time"; };
private:
using option_t = std::pair<std::string, int32_t>;
std::vector<option_t> which_options = {{"1st", 0}, {"2nd", 1}, {"3rd", 2}, {"4th", 3}, {"Last", 4}};
std::vector<option_t> weekday_options = {{"Sun", 0}, {"Mon", 1}, {"Tue", 2}, {"Wed", 3}, {"Thu", 4}, {"Fri", 5}, {"Sat", 6}};
std::vector<option_t> month_options = {{"Jan", 1}, {"Feb", 2}, {"Mar", 3}, {"Apr", 4}, {"May", 5}, {"Jun", 6}, {"Jul", 7}, {"Aug", 8}, {"Sep", 9}, {"Oct", 10}, {"Nov", 11}, {"Dec", 12}};
Labels labels{
{{1 * 8, 1 * 16}, "Adjust the RTC clock date &", Color::light_grey()},
{{1 * 8, 2 * 16}, "time. If clock resets after", Color::light_grey()},
{{1 * 8, 3 * 16}, "reboot, coin batt. is dead. ", Color::light_grey()},
{{5 * 8, 8 * 16 - 2}, "YYYY-MM-DD HH:MM:SS", Color::grey()},
{{9 * 8, 9 * 16}, "- - : :", Color::light_grey()}};
{{1 * 8, 5 * 16 - 2}, "YYYY-MM-DD HH:MM:SS DoW DoY", Color::grey()},
{{5 * 8, 6 * 16}, "- - : :", Color::light_grey()},
{{1 * 8, 11 * 16}, "DST adds 1 hour to RTC time.", Color::light_grey()},
{{0 * 8, 12 * 16}, "Start: 0:00 on Nth DDD in", Color::light_grey()},
{{0 * 8, 13 * 16}, "End: 1:00 on Nth DDD in", Color::light_grey()}};
NumberField field_year{
{5 * 8, 9 * 16},
{1 * 8, 6 * 16},
4,
{2015, 2099},
1,
'0',
true,
};
NumberField field_month{
{10 * 8, 9 * 16},
{6 * 8, 6 * 16},
2,
{1, 12},
1,
'0',
true,
};
NumberField field_day{
{13 * 8, 9 * 16},
{9 * 8, 6 * 16},
2,
{1, 31},
1,
'0',
true,
};
NumberField field_hour{
{16 * 8, 9 * 16},
{12 * 8, 6 * 16},
2,
{0, 23},
1,
'0',
true,
};
NumberField field_minute{
{19 * 8, 9 * 16},
{15 * 8, 6 * 16},
2,
{0, 59},
1,
'0',
true,
};
NumberField field_second{
{22 * 8, 9 * 16},
{18 * 8, 6 * 16},
2,
{0, 59},
1,
'0',
true,
};
Text text_weekday{
{22 * 8, 6 * 16, 3 * 8, 16},
""};
Text text_day_of_year{
{26 * 8, 6 * 16, 3 * 8, 16},
""};
Checkbox checkbox_dst_enable{
{2 * 8, 9 * 16},
23,
"Enable Daylight Savings"};
OptionsField options_dst_start_which{
{15 * 8, 12 * 16},
4,
{}};
OptionsField options_dst_start_weekday{
{20 * 8, 12 * 16},
3,
{}};
OptionsField options_dst_start_month{
{27 * 8, 12 * 16},
3,
{}};
OptionsField options_dst_end_which{
{15 * 8, 13 * 16},
4,
{}};
OptionsField options_dst_end_weekday{
{20 * 8, 13 * 16},
3,
{}};
OptionsField options_dst_end_month{
{27 * 8, 13 * 16},
3,
{}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -564,6 +620,53 @@ class SetPersistentMemoryView : public View {
};
};
class AppSettingsView : public View {
public:
AppSettingsView(NavigationView& nav);
std::string title() const override { return "App Settings"; };
void focus() override;
private:
NavigationView& nav_;
Labels labels{
{{0, 4}, "Select file to edit:", Color::white()}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
true};
};
class SetConfigModeView : public View {
public:
SetConfigModeView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Config Mode"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Controls whether firmware", Color::light_grey()},
{{1 * 8, 2 * 16}, "will enter Config Mode", Color::light_grey()},
{{1 * 8, 3 * 16}, "after a boot failure.", Color::light_grey()},
};
Checkbox checkbox_config_mode_enabled{
{2 * 8, 6 * 16},
16,
"Config Mode enable"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SettingsMenuView : public BtnGridView {
public:
SettingsMenuView(NavigationView& nav);
+15 -1
View File
@@ -90,7 +90,7 @@ SondeView::SondeView(NavigationView& nav)
};
button_see_map.on_select = [this, &nav](Button&) {
nav.push<GeoMapView>(
geomap_view_ = nav.push<GeoMapView>(
sonde_id,
gps_info.alt,
GeoPos::alt_unit::METERS,
@@ -98,6 +98,9 @@ SondeView::SondeView(NavigationView& nav)
gps_info.lat,
gps_info.lon,
999); // set a dummy heading out of range to draw a cross...probably not ideal?
nav.set_on_pop([this]() {
geomap_view_ = nullptr;
});
};
logger = std::make_unique<SondeLogger>();
@@ -124,6 +127,17 @@ SondeView::~SondeView() {
audio::output::stop();
}
void SondeView::on_gps(const GPSPosDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
}
void SondeView::on_orientation(const OrientationDataMessage* msg) {
if (!geomap_view_)
return;
geomap_view_->update_my_orientation(msg->angle, true);
}
void SondeView::focus() {
field_frequency.focus();
}
+19
View File
@@ -60,6 +60,8 @@ class SondeView : public View {
SondeView(NavigationView& nav);
~SondeView();
SondeView(const SondeView& other) = delete;
SondeView& operator=(const SondeView& other) = delete;
void focus() override;
@@ -173,6 +175,8 @@ class SondeView : public View {
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
"See on map"};
GeoMapView* geomap_view_{nullptr};
MessageHandlerRegistration message_handler_packet{
Message::ID::SondePacket,
[this](Message* const p) {
@@ -181,6 +185,21 @@ class SondeView : public View {
this->on_packet(packet);
}};
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
[this](Message* const p) {
const auto message = static_cast<const GPSPosDataMessage*>(p);
this->on_gps(message);
}};
MessageHandlerRegistration message_handler_orientation{
Message::ID::OrientationData,
[this](Message* const p) {
const auto message = static_cast<const OrientationDataMessage*>(p);
this->on_orientation(message);
}};
void on_gps(const GPSPosDataMessage* msg);
void on_orientation(const OrientationDataMessage* msg);
void on_packet(const sonde::Packet& packet);
char* float_to_char(float x, char* p);
};
+11 -3
View File
@@ -27,15 +27,23 @@
void config_mode_blink_until_dfu();
void config_mode_set() {
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_GUARD_VALUE);
uint32_t cms = portapack::persistent_memory::config_mode_storage_direct();
if ((cms >= CONFIG_MODE_GUARD_VALUE) && (cms < CONFIG_MODE_LIMIT_VALUE))
cms += 1;
else
cms = CONFIG_MODE_GUARD_VALUE;
portapack::persistent_memory::set_config_mode_storage_direct(cms);
}
bool config_mode_should_enter() {
return portapack::persistent_memory::config_mode_storage() == CONFIG_MODE_GUARD_VALUE;
if (portapack::persistent_memory::config_disable_config_mode_direct())
return false;
else
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_LIMIT_VALUE;
}
void config_mode_clear() {
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_NORMAL_VALUE);
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_NORMAL_VALUE);
}
uint32_t blink_patterns[] = {
+9
View File
@@ -28,9 +28,18 @@
#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
// number of boot failures before entering config menu mode
#define BOOT_FAILURES_BEFORE_CONFIG_MODE 3
#define CONFIG_MODE_GUARD_VALUE 0xbadb0000
#define CONFIG_MODE_LIMIT_VALUE (CONFIG_MODE_GUARD_VALUE + BOOT_FAILURES_BEFORE_CONFIG_MODE - 1)
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
void config_mode_set();
bool config_mode_should_enter();
void config_mode_clear();
void config_mode_enable(bool v);
bool config_mode_disabled();
void config_mode_run();
+1 -1
View File
@@ -275,7 +275,7 @@ bool memory_dump(uint32_t* addr_start, uint32_t num_words, bool stack_flag) {
int n{0};
bool data_found{false};
make_new_directory(debug_dir);
ensure_directory(debug_dir);
filename = next_filename_matching_pattern(debug_dir + "/" + (stack_flag ? "STACK" : "MEMORY") + "_DUMP_????.TXT");
error = filename.empty();
if (!error)
+68 -3
View File
@@ -113,7 +113,6 @@ void EventDispatcher::run() {
while (is_running) {
const auto events = wait();
dispatch(events);
portapack::usb_serial.dispatch();
}
}
@@ -151,6 +150,8 @@ void EventDispatcher::dispatch(const eventmask_t events) {
*(uint32_t*)&shared_memory.bb_data.data[4]);
}
handle_shell();
if (events & EVT_MASK_APPLICATION) {
handle_application_queue();
}
@@ -163,6 +164,9 @@ void EventDispatcher::dispatch(const eventmask_t events) {
handle_rtc_tick();
}
handle_usb_transfer();
handle_usb();
if (events & EVT_MASK_SWITCHES) {
handle_switches();
}
@@ -216,6 +220,34 @@ void EventDispatcher::handle_rtc_tick() {
portapack::persistent_memory::cache::persist();
}
void EventDispatcher::handle_usb() {
portapack::usb_serial.dispatch();
}
void EventDispatcher::handle_usb_transfer() {
portapack::usb_serial.dispatch_transfer();
}
void EventDispatcher::handle_shell() {
if (waiting_for_shellmode) {
waiting_for_shellmode = false;
shellmode_active = true;
while (shellmode_active) {
chThdSleepMilliseconds(5);
}
}
if (injected_touch_event != nullptr) {
on_touch_event(*injected_touch_event);
injected_touch_event = nullptr;
}
if (injected_keyboard_event != nullptr) {
on_keyboard_event(*injected_keyboard_event);
injected_keyboard_event = nullptr;
}
}
ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent event) {
if (!w->hidden()) {
// To achieve reverse depth ordering (last object drawn is
@@ -239,11 +271,17 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
}
void EventDispatcher::emulateTouch(ui::TouchEvent event) {
on_touch_event(event);
injected_touch_event = &event;
while (injected_touch_event != nullptr) {
chThdSleepMilliseconds(5);
}
}
void EventDispatcher::emulateKeyboard(ui::KeyboardEvent event) {
on_keyboard_event(event);
injected_keyboard_event = &event;
while (injected_keyboard_event != nullptr) {
chThdSleepMilliseconds(5);
}
}
void EventDispatcher::on_keyboard_event(ui::KeyboardEvent event) {
@@ -283,6 +321,8 @@ ui::Widget* EventDispatcher::getFocusedWidget() {
}
void EventDispatcher::handle_lcd_frame_sync() {
bool waiting_for_frame = this->waiting_for_frame;
DisplayFrameSyncMessage message;
message_map.send(&message);
@@ -290,6 +330,28 @@ void EventDispatcher::handle_lcd_frame_sync() {
painter.paint_widget_tree(top_widget);
portapack::backlight()->on();
if (waiting_for_frame)
this->waiting_for_frame = false;
}
void EventDispatcher::wait_finish_frame() {
waiting_for_frame = true;
while (waiting_for_frame) {
chThdSleepMilliseconds(5);
}
}
void EventDispatcher::enter_shell_working_mode() {
waiting_for_shellmode = true;
while (waiting_for_shellmode) {
chThdSleepMilliseconds(5);
}
}
void EventDispatcher::exit_shell_working_mode() {
shellmode_active = false;
}
void EventDispatcher::handle_switches() {
@@ -351,6 +413,9 @@ void EventDispatcher::handle_encoder() {
const uint32_t encoder_now = get_encoder_position();
const int32_t delta = static_cast<int32_t>(encoder_now - encoder_last);
if (delta == 0)
return;
encoder_last = encoder_now;
const auto event = static_cast<ui::EncoderEvent>(delta);
event_bubble_encoder(event);
+13
View File
@@ -45,6 +45,7 @@ constexpr auto EVT_MASK_ENCODER = EVENT_MASK(4);
constexpr auto EVT_MASK_TOUCH = EVENT_MASK(5);
constexpr auto EVT_MASK_APPLICATION = EVENT_MASK(6);
constexpr auto EVT_MASK_LOCAL = EVENT_MASK(7);
constexpr auto EVT_MASK_USB = EVENT_MASK(8);
class EventDispatcher {
public:
@@ -89,6 +90,10 @@ class EventDispatcher {
void emulateTouch(ui::TouchEvent event);
void emulateKeyboard(ui::KeyboardEvent event);
void wait_finish_frame();
void enter_shell_working_mode();
void exit_shell_working_mode();
ui::Widget* getTopWidget();
ui::Widget* getFocusedWidget();
@@ -104,6 +109,11 @@ class EventDispatcher {
bool sd_card_present = false;
static bool display_sleep;
bool in_key_event = false;
volatile bool waiting_for_frame = false;
volatile bool waiting_for_shellmode = false;
volatile bool shellmode_active = false;
ui::TouchEvent* volatile injected_touch_event = nullptr;
ui::KeyboardEvent* volatile injected_keyboard_event = nullptr;
eventmask_t wait();
void dispatch(const eventmask_t events);
@@ -111,6 +121,9 @@ class EventDispatcher {
void handle_application_queue();
void handle_local_queue();
void handle_rtc_tick();
void handle_usb();
void handle_usb_transfer();
void handle_shell();
static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event);
+6
View File
@@ -54,6 +54,11 @@ set(EXTCPPSRC
external/spainter/ui_spectrum_painter.cpp
external/spainter/ui_spectrum_painter_text.cpp
external/spainter/ui_spectrum_painter_image.cpp
#keyfob
external/keyfob/main.cpp
external/keyfob/ui_keyfob.cpp
external/keyfob/ui_keyfob.hpp
)
set(EXTAPPLIST
@@ -70,4 +75,5 @@ set(EXTAPPLIST
jammer
gpssim
spainter
keyfob
)
+7
View File
@@ -30,6 +30,7 @@ MEMORY
ram_external_app_jammer(rwx) : org = 0xEEF30000, len = 32k
ram_external_app_gpssim(rwx) : org = 0xEEF40000, len = 32k
ram_external_app_spainter(rwx) : org = 0xEEF50000, len = 32k
ram_external_app_keyfob(rwx) : org = 0xEEF60000, len = 32k
}
SECTIONS
@@ -116,4 +117,10 @@ SECTIONS
*(*ui*external_app*spainter*);
} > ram_external_app_spainter
.external_app_keyfob : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_keyfob.application_information));
*(*ui*external_app*keyfob*);
} > ram_external_app_keyfob
}
+82
View File
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_keyfob.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::keyfob {
void initialize_app(ui::NavigationView& nav) {
nav.push<KeyfobView>();
}
} // namespace ui::external_app::keyfob
extern "C" {
__attribute__((section(".external_app.app_keyfob.application_information"), used)) application_information_t _application_information_keyfob = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::keyfob::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Keyfob",
/*.bitmap_data = */ {
0x30,
0x00,
0x30,
0x00,
0x30,
0x00,
0x30,
0x00,
0x30,
0x00,
0x30,
0x00,
0xFC,
0x00,
0xCE,
0x01,
0x86,
0x01,
0xFE,
0x01,
0x86,
0x31,
0x86,
0x49,
0xCE,
0x87,
0xFC,
0x84,
0xFC,
0x4B,
0x78,
0x30,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::TX,
/*.m4_app_tag = portapack::spi_flash::image_tag_keyfob */ {'P', 'O', 'O', 'K'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -27,7 +27,7 @@
using namespace portapack;
namespace ui {
namespace ui::external_app::keyfob {
uint8_t KeyfobView::subaru_get_checksum() {
uint8_t checksum = 0;
@@ -239,4 +239,4 @@ KeyfobView::KeyfobView(
};
}
} /* namespace ui */
} /* namespace ui::external_app::keyfob */
@@ -29,7 +29,7 @@
using namespace encoders;
namespace ui {
namespace ui::external_app::keyfob {
class KeyfobView : public View {
public:
@@ -126,4 +126,4 @@ class KeyfobView : public View {
}};
};
} /* namespace ui */
} /* namespace ui::external_app::keyfob */
+21 -7
View File
@@ -205,11 +205,13 @@ class Sprite {
byte who;
byte _speed;
byte dir;
byte lastDir;
byte phase;
// Sprite bits
byte palette2; // 4->16 color map index
byte bits; // index of sprite bits
signed char sy;
void Init(const byte* s) {
@@ -274,9 +276,17 @@ class Sprite {
f = pgm_read_byte(_pacLeftAnim + f);
else if (dir == MRight)
f = pgm_read_byte(_pacRightAnim + f);
else
else if (dir == MDown || dir == MUp)
f = pgm_read_byte(_pacVAnim + f);
if (dir == MUp)
else if (dir == MStopped) {
if (lastDir == MLeft)
f = pgm_read_byte(_pacLeftAnim + f);
else if (lastDir == MRight)
f = pgm_read_byte(_pacRightAnim + f);
else if (lastDir == MDown || lastDir == MUp)
f = pgm_read_byte(_pacVAnim + f);
}
if ((dir == MUp) || (dir == MStopped && lastDir == MUp))
sy = -1;
bits = f + PACMANSPRITE;
}
@@ -838,15 +848,19 @@ class Playfield {
else if (DEMO == 0 && s->who == PACMAN && choice[3] < 0x7FFF && but_RIGHT)
dir = MRight;
else if (DEMO == 0 && choice[0] < 0x7FFF && s->who == PACMAN && dir == MUp)
else if (DEMO == 0 && choice[0] < 0x7FFF && s->who == PACMAN && dir == MUp) {
dir = MUp;
else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft)
s->lastDir = MUp;
} else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft) {
dir = MLeft;
else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown)
s->lastDir = MLeft;
} else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown) {
dir = MDown;
else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight)
s->lastDir = MDown;
} else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight) {
dir = MRight;
else if ((DEMO == 0 && s->who != PACMAN) || DEMO == 1) {
s->lastDir = MRight;
} else if ((DEMO == 0 && s->who != PACMAN) || DEMO == 1) {
// Don't choose opposite of current direction?
int16_t dist = choice[4 - dir]; // favor current direction
+2 -7
View File
@@ -33,10 +33,10 @@ namespace max283x {
namespace lo {
constexpr std::array<rf::FrequencyRange, 4> band{{
{2300000000, 2400000000},
{2170000000, 2400000000},
{2400000000, 2500000000},
{2500000000, 2600000000},
{2600000000, 2700000000},
{2600000000, 2740000000},
}};
} /* namespace lo */
@@ -48,11 +48,6 @@ namespace lna {
constexpr range_t<int8_t> gain_db_range{0, 40};
constexpr int8_t gain_db_step = 8;
constexpr std::array<rf::FrequencyRange, 2> band{{
{2300000000, 2500000000},
{2500000000, 2700000000},
}};
} /* namespace lna */
/*************************************************************************/
+2 -1
View File
@@ -177,7 +177,7 @@ enum class PortaPackModel {
static bool save_config(int8_t value) {
persistent_memory::set_config_cpld(value);
if (sd_card::status() == sd_card::Status::Mounted) {
make_new_directory("/hardware");
ensure_directory("/hardware");
File file;
auto sucess = file.create("/hardware/settings.txt");
if (!sucess.is_valid()) {
@@ -411,6 +411,7 @@ bool init() {
/* Cache some configuration data from persistent memory. */
persistent_memory::cache::init();
rtc_time::dst_init();
chThdSleepMilliseconds(10);
clock_manager.init_clock_generator();
+2 -10
View File
@@ -116,16 +116,8 @@ void set_direction(const rf::Direction new_direction) {
/* TODO: Refactor all the various "Direction" enumerations into one. */
/* TODO: Only make changes if direction changes, but beware of clock enabling. */
// Hack to fix the CPLD (clocking ?) bug: toggle CPLD SRAM overlay depending on new direction.
// Use CPLD's EEPROM config when transmitting
// Use the SRAM overlay when receiving
if (direction != new_direction) {
if (new_direction == rf::Direction::Transmit)
hackrf::cpld::init_from_eeprom();
else
// Prevents ghosting when switching back to RX from TX mode.
hackrf::cpld::load_sram_no_verify();
}
// Prevents ghosting when switching back to RX from TX mode.
hackrf::cpld::load_sram_no_verify();
direction = new_direction;
+216
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2024 Mark Thompson
*
* This file is part of PortaPack.
*
@@ -20,24 +21,239 @@
*/
#include "rtc_time.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
namespace pmem = portapack::persistent_memory;
namespace rtc_time {
Signal<> signal_tick_second;
bool dst_enabled{false};
bool dst_in_range{false};
uint16_t dst_start_doy;
uint16_t dst_end_doy;
uint16_t dst_current_doy{0xFFFF};
uint16_t dst_current_year{0xFFFF};
static const uint16_t months_with_31 = 0x0AD5; // Bits represents months with 31 days (bit 0 for January, etc)
void on_tick_second() {
signal_tick_second.emit();
}
// Check if DST is configured and active (called at power-up)
void dst_init() {
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
dst_update_date_range(datetime.year(), LPC_RTC->DOY);
}
// Return current time & date (adjusted for DST if enabled)
rtc::RTC now() {
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
if (dst_enabled) {
datetime = dst_adjust_returned_time(datetime);
}
return datetime;
}
rtc::RTC now(rtc::RTC& out_datetime) {
rtcGetTime(&RTCD1, &out_datetime);
if (dst_enabled) {
out_datetime = dst_adjust_returned_time(out_datetime);
}
return out_datetime;
}
// Add 1 hour (spring forward) if needed for DST (called whenever RTC is read if DST is enabled)
rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime) {
// Read day of year from RTC and check for change
uint32_t doy = LPC_RTC->DOY;
// Update DST start/end day-of-year only when year changes, otherwise just check if in range when day changes
if (doy != dst_current_doy) {
if (datetime.year() != dst_current_year)
dst_update_date_range(datetime.year(), doy);
else
dst_check_date_range(doy);
}
// Not in DST date range
if (!dst_in_range)
return datetime;
// Add 1 hour to RTC time
uint16_t year = datetime.year();
uint8_t month = datetime.month();
uint8_t day = datetime.day();
uint8_t hour = datetime.hour();
if (++hour > 23) {
hour = 0;
if (++day > days_per_month(year, month)) {
day = 1;
if (++month > 12) {
year++;
}
}
}
rtc::RTC dst_datetime{year, month, day, hour, datetime.minute(), datetime.second()};
return dst_datetime;
}
// Check if current date is within the DST range (called when date changes)
void dst_check_date_range(uint16_t doy) {
dst_current_doy = doy;
// Check if date is within DST range
// (note that dates are reversed in Southern hemisphere because Summer starts in December)
if (dst_start_doy <= dst_end_doy)
dst_in_range = ((doy >= dst_start_doy) && (doy < dst_end_doy));
else
dst_in_range = ((doy >= dst_start_doy) || (doy < dst_end_doy));
}
// Update DST parameters (called at power-up, when year changes, or DST settings are changed)
void dst_update_date_range(uint16_t year, uint16_t doy) {
dst_enabled = pmem::dst_enabled();
if (dst_enabled) {
const pmem::dst_config_t dst = pmem::config_dst();
dst_current_year = year;
dst_start_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.start_month, dst.b.start_which, dst.b.start_weekday);
dst_end_doy = day_of_year_of_nth_weekday(dst_current_year, dst.b.end_month, dst.b.end_which, dst.b.end_weekday);
dst_check_date_range(doy);
} else {
dst_in_range = false;
}
}
// Set RTC clock.
// If the user has enabled DST, the time entered and passed to this function is interpreted as having been adjusted for DST.
// When DST functionality is enabled in pmem, the value stored in the RTC hardware is non-DST time, and we only fudge the time when read.
// Thus, it's necessary to subtract an hour before storing it in the RTC if we're in the DST date range.
// (If RTC is desired to represent UTC, then DST should obviously be disabled in settings.)
// NB: Firmware should not change RTC without going through this function.
void set(rtc::RTC& new_datetime) {
uint16_t year = new_datetime.year();
uint8_t month = new_datetime.month();
uint8_t day = new_datetime.day();
uint8_t hour = new_datetime.hour();
// NB: DST code relies on the Day of Year (DOY) value to be initialized in the RTC by firmware (RTC hardware only does increment and wrap)
uint16_t doy = day_of_year(year, month, day);
dst_update_date_range(year, doy);
// NB: Currently we only support the DST transition at midnight RTC time (not configurable to hour granularity).
// Note on handling the the hour before/after DST time change:
//
// If entered hour==0 on dst_start_day:
// Time entered is INVALID due to spring-forward; code below rolls back RTC to last hour of previous day.
//
// If entered hour==0 on dst_end_doy:
// This hour occurs twice due to fall-back; code below treats as if DST has ended (the second occurrence) and doesn't roll back RTC
//
if (dst_in_range) {
// Subtract 1 hour from requested time before storing in RTC
if (hour-- == 0) {
hour = 23;
if (day-- == 0) {
if (month-- == 0) {
month = 12;
year--;
}
day = days_per_month(year, month);
}
}
// Update day-of-year if date was changed above
if (day != new_datetime.day()) {
doy = day_of_year(year, month, day);
dst_update_date_range(year, doy);
}
}
// NB: Writing RTC twice takes a second, but ensures that new value can be read back immediately
// (if not written twice, the old value will be returned if read back in the following 1-2 seconds)
// (you will notice with older Mayhem versions that running the Date/Time Settings app again quickly will show the old time)
LPC_RTC->DOY = doy;
rtc::RTC adjusted_datetime{year, month, day, hour, new_datetime.minute(), new_datetime.second()};
rtcSetTime(&RTCD1, &adjusted_datetime);
rtcSetTime(&RTCD1, &adjusted_datetime);
}
// Calculates 1-based day of year for Nth weekday in month (weekday and n are 0-based, month is 1-based)
uint16_t day_of_year_of_nth_weekday(uint16_t year, uint8_t month, uint8_t n, uint8_t weekday) {
uint8_t w = day_of_week(year, month, 1);
uint8_t nn = n;
// special handling for "last" weekday - are there 4 or 5 in the month?
if (n == 4) {
if (month == 2) {
// February
// only weekday w occurs 5 times on the 29th on leap years only
if ((weekday != w) || !leap_year(year))
nn = 3;
} else {
// Other months have either 30 or 31 days;
// weekdays w and w+1 occur 5 times (on the 29th & 30th); weekday w+2 occurs 5 times only if month has 31 days
if ((weekday != w) && (weekday != (w + 1) % 7) &&
((weekday != (w + 2) % 7) || ((months_with_31 & (1 << (month - 1))) == 0)))
nn = 3;
}
}
return day_of_year(year, month, 1) + (nn * 7) + weekday + ((weekday < w) ? 7 : 0) - w;
}
// Calculates 1-based day of year (input month & day are 1-based)
uint16_t day_of_year(uint16_t year, uint8_t month, uint8_t day) {
// 1-based day of year for 1st day or month (index is 1-based month)
static uint16_t month_to_day_in_year[1 + 12] = {
0, // placeholder for 1-based indexing
1,
1 + 31,
1 + 31 + 28,
1 + 31 + 28 + 31,
1 + 31 + 28 + 31 + 30,
1 + 31 + 28 + 31 + 30 + 31,
1 + 31 + 28 + 31 + 30 + 31 + 30,
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31,
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
};
return month_to_day_in_year[month] + day - (((month > 2) && leap_year(year)) ? 0 : 1);
}
bool leap_year(uint16_t year) {
// Technically should be: ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)
// but year 2100 is a long way off and the LPC43xx RTC doesn't bother handling it either
return ((year & 3) == 0);
}
uint8_t days_per_month(uint16_t year, uint8_t month) {
if (month == 2)
return leap_year(year) ? 29 : 28;
else
return ((months_with_31 & (1 << (month - 1))) != 0) ? 31 : 30;
}
uint8_t current_day_of_week() {
rtc::RTC datetime;
now(datetime);
return day_of_week(datetime.year(), datetime.month(), datetime.day());
}
// Returns 0-based weekday for date (0=Sunday, 1=Monday, etc) - using Zeller's Congruence formula
// (month and day are 1-based)
uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day) {
int m = (month < 3) ? month + 13 : month + 1;
int y = (month < 3) ? year - 1 : year;
return (day - 1 + (13 * m / 5) + y + (y / 4) - (y / 100) + (y / 400)) % 7;
}
} /* namespace rtc_time */
+15
View File
@@ -33,12 +33,27 @@ extern Signal<> signal_tick_second;
void on_tick_second();
/* Sets the current RTCTime in the RTC. */
void set(rtc::RTC& new_datetime);
/* Returns the current RTCTime from the RTC. */
rtc::RTC now();
/* Returns the current RTCTime from the RTC. */
rtc::RTC now(rtc::RTC& out_datetime);
/* Daylight Savings Time functions */
void dst_init();
rtc::RTC dst_adjust_returned_time(rtc::RTC& datetime);
void dst_check_date_range(uint16_t doy);
void dst_update_date_range(uint16_t year, uint16_t doy);
uint8_t days_per_month(uint16_t year, uint8_t month);
uint8_t current_day_of_week();
uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day);
bool leap_year(uint16_t year);
uint16_t day_of_year(uint16_t year, uint8_t month, uint8_t day);
uint16_t day_of_year_of_nth_weekday(uint16_t year, uint8_t month, uint8_t n, uint8_t weekday);
} /* namespace rtc_time */
#endif /*__RTC_TIME_H__*/
+2 -2
View File
@@ -185,7 +185,7 @@ std::string to_string_freq(const uint64_t f) {
// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
std::string to_string_short_freq(const uint64_t f) {
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
auto final_str = to_string_dec_int((f + 50) / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
return final_str;
}
@@ -210,7 +210,7 @@ std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
uint32_t divisor = pow10[6 - precision];
final_str = to_string_dec_uint(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
final_str = to_string_dec_uint((f + (divisor / 2)) / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
}
return final_str;
}
+1 -1
View File
@@ -85,7 +85,7 @@ const tone_key_t tone_keys = {
{"Senn. 32.768k", F2Ix100(32768.0)}};
std::string fx100_string(uint32_t f) {
return to_string_dec_uint(f / 100) + "." + to_string_dec_uint(((f + 5) / 10) % 10);
return to_string_dec_uint((f + 5) / 100) + "." + to_string_dec_uint(((f + 5) / 10) % 10);
}
float tone_key_frequency(tone_index index) {
+248 -108
View File
@@ -65,6 +65,7 @@ GeoPos::GeoPos(
set_lon(0);
const auto changed_fn = [this](int32_t) {
// Convert degrees/minutes/seconds fields to decimal (floating point) lat/lon degree
float lat_value = lat();
float lon_value = lon();
@@ -84,6 +85,27 @@ GeoPos::GeoPos(
field_lon_minutes.on_change = changed_fn;
field_lon_seconds.on_change = changed_fn;
const auto wrapped_lat_seconds = [this](int32_t v) {
field_lat_minutes.on_encoder(v);
};
const auto wrapped_lat_minutes = [this](int32_t v) {
field_lat_degrees.on_encoder((field_lat_degrees.value() >= 0) ? v : -v);
};
const auto wrapped_lon_seconds = [this](int32_t v) {
field_lon_minutes.on_encoder(v);
};
const auto wrapped_lon_minutes = [this](int32_t v) {
field_lon_degrees.on_encoder((field_lon_degrees.value() >= 0) ? v : -v);
};
field_lat_seconds.on_wrap = wrapped_lat_seconds;
field_lat_minutes.on_wrap = wrapped_lat_minutes;
field_lon_seconds.on_wrap = wrapped_lon_seconds;
field_lon_minutes.on_wrap = wrapped_lon_minutes;
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
if (speed_unit_ == MPH) text_speed_unit.set("mph");
@@ -173,12 +195,8 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
if (map_zoom == -2) {
map_zoom = 1;
} else {
map_zoom++;
// When zooming in, ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
if ((map_zoom > 1) && (geomap_rect_width % map_zoom != 0)) {
map_zoom--;
}
// zoom in faster after exceeding the map resolution limit
map_zoom += (map_zoom >= MAP_ZOOM_RESOLUTION_LIMIT) ? map_zoom : 1;
}
}
} else if (delta < 0) {
@@ -186,15 +204,21 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
if (map_zoom == 1) {
map_zoom = -2;
} else {
map_zoom--;
if (map_zoom > MAP_ZOOM_RESOLUTION_LIMIT)
map_zoom /= 2;
else
map_zoom--;
}
}
} else {
return false;
}
map_visible = map_opened && (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT);
zoom_pixel_offset = (map_visible && (map_zoom > 1)) ? (float)map_zoom / 2 : 0.0f;
// Trigger map redraw
markerListUpdated = true;
redraw_map = true;
set_dirty();
return true;
}
@@ -208,7 +232,9 @@ void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
// Zoom in: Expand each pixel to "map_zoom" number of pixels.
// Future TODO: Add dithering to smooth out the pixelation.
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
// For 240 width, than means no check is needed for map_zoom values up to 6
// For 240 width, than means no check is needed for map_zoom values up to 6.
// (Rectangle height must also divide evenly into map_zoom or we get black lines at end of screen)
// Note that zooming in results in a map offset of (1/map_zoom) pixels to the right & downward directions (see zoom_pixel_offset).
for (int i = (geomap_rect_width / map_zoom) - 1; i >= 0; i--) {
for (int j = 0; j < map_zoom; j++) {
buffer[(i * map_zoom) + j] = buffer[i];
@@ -228,90 +254,152 @@ void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
}
void GeoMap::draw_markers(Painter& painter) {
for (int i = 0; i < markerListLen; ++i) {
draw_marker_item(painter, markerList[i], Color::blue(), Color::blue(), Color::magenta());
}
}
void GeoMap::draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor, const Color backColor) {
const auto r = screen_rect();
const ui::Point itemPoint = item_rect_pixel(item);
if ((itemPoint.x() >= 0) && (itemPoint.x() < r.width()) &&
(itemPoint.y() > 10) && (itemPoint.y() < r.height())) // Dont draw within symbol size of top
{
draw_marker(painter, {itemPoint.x(), itemPoint.y() + r.top()}, item.angle, item.tag, color, fontColor, backColor);
}
}
// Calculate screen position of item, adjusted for zoom factor.
ui::Point GeoMap::item_rect_pixel(GeoMarker& item) {
const auto r = screen_rect();
const auto geomap_rect_half_width = r.width() / 2;
const auto geomap_rect_half_height = r.height() / 2;
GeoPoint mapPoint = lat_lon_to_map_pixel(item.lat, item.lon);
float x = mapPoint.x - x_pos;
float y = mapPoint.y - y_pos;
if (map_zoom > 1) {
x = x * map_zoom + zoom_pixel_offset;
y = y * map_zoom + zoom_pixel_offset;
} else if (map_zoom < 0) {
x = x / (-map_zoom);
y = y / (-map_zoom);
}
x += geomap_rect_half_width;
y += geomap_rect_half_height;
return {(int16_t)x, (int16_t)y};
}
// Converts latitude/longitude to pixel coordinates in map file.
// (Note that when map_zoom==1, one pixel in map file corresponds to 1 pixel on screen)
GeoPoint GeoMap::lat_lon_to_map_pixel(float lat, float lon) {
// Using WGS 84/Pseudo-Mercator projection
float x = (map_width * (lon + 180) / 360);
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
double lat_rad = sin(lat * pi / 180);
float y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset));
return {x, y};
}
// Draw grid in place of map (when zoom-in level is too high).
void GeoMap::draw_map_grid() {
const auto r = screen_rect();
for (int i = 0; i < markerListLen; ++i) {
GeoMarker& item = markerList[i];
double lat_rad = sin(item.lat * pi / 180);
int x = (map_width * (item.lon + 180) / 360) - x_pos;
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
// Grid spacing is just based on zoom at the moment, and centered on screen.
// TODO: Maybe align with latitude/longitude seconds instead?
int grid_spacing = map_zoom * 2;
int x = (r.width() / 2) % grid_spacing;
int y = (r.height() / 2) % grid_spacing;
if (map_zoom > 1) {
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
} else if (map_zoom < 0) {
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
}
if (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT)
return;
if ((x >= 0) && (x < r.width()) &&
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
{
ui::Point itemPoint(x, y + r.top());
if (y >= 32) { // Dont draw text if it would overlap top
// Text and symbol
draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta());
} else {
// Only symbol
draw_bearing(itemPoint, item.angle, 10, Color::blue());
}
}
display.fill_rectangle({{0, r.top()}, {r.width(), r.height()}}, Color::black());
for (uint16_t line = y; line < r.height(); line += grid_spacing) {
display.fill_rectangle({{0, r.top() + line}, {r.width(), 1}}, Color::darker_grey());
}
for (uint16_t column = x; column < r.width(); column += grid_spacing) {
display.fill_rectangle({{column, r.top()}, {1, r.height()}}, Color::darker_grey());
}
}
void GeoMap::paint(Painter& painter) {
const auto r = screen_rect();
std::array<ui::Color, 240> map_line_buffer;
std::array<ui::Color, geomap_rect_width> map_line_buffer;
int16_t zoom_seek_x, zoom_seek_y;
// Ony redraw map if it moved by at least 1 pixel
// or the markers list was updated
int x_diff = abs(x_pos - prev_x_pos);
int y_diff = abs(y_pos - prev_y_pos);
// Ony redraw map if it moved by at least 1 pixel or the markers list was updated
if (map_zoom <= 1) {
// Zooming out, or no zoom
const int min_diff = abs(map_zoom);
if ((int)abs(x_pos - prev_x_pos) >= min_diff)
redraw_map = true;
else if ((int)abs(y_pos - prev_y_pos) >= min_diff)
redraw_map = true;
} else {
// Zooming in; magnify position differences (utilizing zoom_pixel_offset)
if ((int)(abs(x_pos - prev_x_pos) * map_zoom) >= 1)
redraw_map = true;
else if ((int)(abs(y_pos - prev_y_pos) * map_zoom) >= 1)
redraw_map = true;
}
if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) {
int32_t zoom_seek_x = x_pos;
int32_t zoom_seek_y = y_pos;
// Adjust starting corner position of map per zoom setting
if (map_zoom > 1) {
zoom_seek_x += (r.width() - (r.width() / map_zoom)) / 2;
zoom_seek_y += (r.height() - (r.height() / map_zoom)) / 2;
} else if (map_zoom < 0) {
zoom_seek_x += (r.width() - (r.width() * (-map_zoom))) / 2;
zoom_seek_y += (r.height() - (r.height() * (-map_zoom))) / 2;
}
// Read from map file and display to zoomed scale
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
map_read_line(map_line_buffer.data(), r.width());
for (uint16_t j = 0; j < duplicate_lines; j++) {
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
}
}
prev_x_pos = x_pos;
if (redraw_map) {
prev_x_pos = x_pos; // Note x_pos/y_pos pixel position in map file now correspond to screen rect CENTER pixel
prev_y_pos = y_pos;
redraw_map = false;
// Adjust starting corner position of map per zoom setting;
// When zooming in the map should technically by shifted left & up by another map_zoom/2 pixels but
// the map_read_line() function doesn't handle that yet so we're adjusting markers instead (see zoom_pixel_offset).
if (map_zoom > 1) {
zoom_seek_x = x_pos - (float)r.width() / (2 * map_zoom);
zoom_seek_y = y_pos - (float)r.height() / (2 * map_zoom);
} else {
zoom_seek_x = x_pos - (r.width() * abs(map_zoom)) / 2;
zoom_seek_y = y_pos - (r.height() * abs(map_zoom)) / 2;
}
if (map_visible) {
// Read from map file and display to zoomed scale
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
map_read_line(map_line_buffer.data(), r.width());
for (uint16_t j = 0; j < duplicate_lines; j++) {
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
}
}
} else {
// No map data or excessive zoom; just draw a grid
draw_map_grid();
}
// Draw crosshairs in center in manual panning mode
if (manual_panning_) {
display.fill_rectangle({r.center() - Point(16, 1), {32, 2}}, Color::red());
display.fill_rectangle({r.center() - Point(1, 16), {2, 32}}, Color::red());
display.fill_rectangle({r.center() - Point(16, 1) + Point(zoom_pixel_offset, zoom_pixel_offset), {32, 2}}, Color::red());
display.fill_rectangle({r.center() - Point(1, 16) + Point(zoom_pixel_offset, zoom_pixel_offset), {2, 32}}, Color::red());
}
// Draw the other markers
draw_markers(painter);
draw_scale(painter);
markerListUpdated = false;
draw_mypos(painter);
set_clean();
}
// Draw the marker in the center
if (!manual_panning_) {
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
if (!manual_panning_ && !hide_center_marker_) {
draw_marker(painter, r.center() + Point(zoom_pixel_offset, zoom_pixel_offset), angle_, tag_, Color::red(), Color::white(), Color::black());
}
}
@@ -335,35 +423,41 @@ bool GeoMap::on_touch(const TouchEvent event) {
}
void GeoMap::move(const float lon, const float lat) {
const auto r = screen_rect();
lon_ = lon;
lat_ = lat;
// Using WGS 84/Pseudo-Mercator projection
x_pos = map_width * (lon_ + 180) / 360 - (geomap_rect_width / 2);
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
double lat_rad = sin(lat * pi / 180);
y_pos = map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset) - 128; // Offset added for the GUI
// Calculate x_pos/y_pos in map file corresponding to CENTER pixel of screen rect
// (Note there is a 1:1 correspondence between map file pixels and screen pixels when map_zoom=1)
GeoPoint mapPoint = lat_lon_to_map_pixel(lat_, lon_);
x_pos = mapPoint.x;
y_pos = mapPoint.y;
// Cap position
if (x_pos > (map_width - geomap_rect_width))
x_pos = map_width - geomap_rect_width;
if (y_pos > (map_height + geomap_rect_height))
y_pos = map_height - geomap_rect_height;
if (x_pos > (map_width - r.width() / 2))
x_pos = map_width - r.width() / 2;
if (y_pos > (map_height + r.height() / 2))
y_pos = map_height - r.height() / 2;
// Scale calculation
float km_per_deg_lon = cos(lat * pi / 180) * 111.321; // 111.321 km/deg longitude at equator, and 0 km at poles
pixels_per_km = (geomap_rect_width / 2) / km_per_deg_lon;
pixels_per_km = (r.width() / 2) / km_per_deg_lon;
}
bool GeoMap::init() {
auto result = map_file.open("ADSB/world_map.bin");
if (result.is_valid())
return false;
map_opened = !result.is_valid();
map_file.read(&map_width, 2);
map_file.read(&map_height, 2);
if (map_opened) {
map_file.read(&map_width, 2);
map_file.read(&map_height, 2);
} else {
map_width = 32768;
map_height = 32768;
}
map_visible = map_opened;
map_center_x = map_width >> 1;
map_center_y = map_height >> 1;
@@ -374,7 +468,7 @@ bool GeoMap::init() {
map_world_lon = map_width / (2 * pi);
map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
return true;
return map_opened;
}
void GeoMap::set_mode(GeoMapMode mode) {
@@ -390,21 +484,35 @@ bool GeoMap::manual_panning() {
}
void GeoMap::draw_scale(Painter& painter) {
uint16_t km = 800;
uint16_t scale_width = (map_zoom > 0) ? km * pixels_per_km * map_zoom : km * pixels_per_km / (-map_zoom);
const auto r = screen_rect();
uint32_t m = 800000;
uint32_t scale_width = (map_zoom > 0) ? m * map_zoom * pixels_per_km : m * pixels_per_km / (-map_zoom);
ui::Color scale_color = (map_visible) ? Color::black() : Color::white();
std::string km_string;
while (scale_width > screen_width / 2) {
while (scale_width > (uint32_t)r.width() * (1000 / 2)) {
scale_width /= 2;
km /= 2;
m /= 2;
}
scale_width /= 1000;
if (m < 1000) {
km_string = to_string_dec_uint(m) + "m";
} else {
m += 50; // (add rounding factor for div by 100 below)
uint32_t km = m / 1000;
m -= km * 1000;
if (m == 0) {
km_string = to_string_dec_uint(km) + " km";
} else {
km_string = to_string_dec_uint(km) + "." + to_string_dec_uint(m / 100, 1) + "km";
}
}
std::string km_string = to_string_dec_uint(km) + " km";
display.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 4}, {(uint16_t)scale_width, 2}}, scale_color);
display.fill_rectangle({{r.right() - 5, r.bottom() - 8}, {2, 6}}, scale_color);
display.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 8}, {2, 6}}, scale_color);
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 4}, {scale_width, 2}}, Color::black());
display.fill_rectangle({{screen_width - 5, screen_height - 8}, {2, 6}}, Color::black());
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 8}, {2, 6}}, Color::black());
painter.draw_string({(uint16_t)(screen_width - 25 - scale_width - km_string.length() * 5 / 2), screen_height - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
painter.draw_string({(uint16_t)(r.right() - 25 - scale_width - km_string.length() * 5 / 2), r.bottom() - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
}
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
@@ -421,9 +529,13 @@ void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t siz
size--;
}
display.draw_pixel(origin, color); // 1 pixel indicating center pivot point of bearing symbol
}
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color, const Color fontColor, const Color backColor) {
const auto r = screen_rect();
int tagOffset = 10;
if (mode_ == PROMPT) {
// Cross
@@ -441,31 +553,38 @@ void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint
tagOffset = 8;
}
// center tag above point
if (itemTag.find_first_not_of(' ') != itemTag.npos) { // only draw tag if we have something other than spaces
if ((itemPoint.y() - r.top() >= 32) && (itemTag.find_first_not_of(' ') != itemTag.npos)) { // only draw tag if doesn't overlap top & not just spaces
painter.draw_string(itemPoint - Point(((int)itemTag.length() * 8 / 2), 14 + tagOffset),
style().font, fontColor, backColor, itemTag);
}
}
void GeoMap::draw_mypos(Painter& painter) {
if ((my_pos.lat < INVALID_LAT_LON) && (my_pos.lon < INVALID_LAT_LON))
draw_marker_item(painter, my_pos, Color::yellow());
}
void GeoMap::clear_markers() {
markerListLen = 0;
}
MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
const auto r = screen_rect();
MapMarkerStored ret;
// Check if it could be on screen
// Only checking one direction to reduce CPU
double lat_rad = sin(marker.lat * pi / 180);
int x = (map_width * (marker.lon + 180) / 360) - x_pos;
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
if (false == ((x >= 0) && (x < geomap_rect_width) && (y > 10) && (y < geomap_rect_height))) // Dont draw within symbol size of top
{
// (Shows more distant planes when zoomed out)
GeoPoint mapPoint = lat_lon_to_map_pixel(marker.lat, marker.lon);
int x_dist = abs((int)mapPoint.x - (int)x_pos);
int y_dist = abs((int)mapPoint.y - (int)y_pos);
int zoom_out = (map_zoom < 0) ? -map_zoom : 1;
if ((x_dist >= (zoom_out * r.width() / 2)) || (y_dist >= (zoom_out * r.height() / 2))) {
ret = MARKER_NOT_STORED;
} else if (markerListLen < NumMarkerListElements) {
markerList[markerListLen] = marker;
markerListLen++;
markerListUpdated = true;
redraw_map = true;
ret = MARKER_STORED;
} else {
ret = MARKER_LIST_FULL;
@@ -473,13 +592,36 @@ MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
return ret;
}
void GeoMap::update_my_position(float lat, float lon, int32_t altitude) {
my_pos.lat = lat;
my_pos.lon = lon;
my_altitude = altitude;
redraw_map = true;
set_dirty();
}
void GeoMap::update_my_orientation(uint16_t angle, bool refresh) {
my_pos.angle = angle;
if (refresh) {
redraw_map = true;
set_dirty();
}
}
void GeoMapView::focus() {
geopos.focus();
if (!map_opened)
if (!geomap.map_file_opened())
nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT);
}
void GeoMapView::update_my_position(float lat, float lon, int32_t altitude) {
geomap.update_my_position(lat, lon, altitude);
}
void GeoMapView::update_my_orientation(uint16_t angle, bool refresh) {
geomap.update_my_orientation(angle, refresh);
}
void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed) {
if (geomap.manual_panning()) {
geomap.set_dirty();
@@ -569,8 +711,7 @@ GeoMapView::GeoMapView(
add_child(&geopos);
map_opened = geomap.init();
if (!map_opened) return;
geomap.init();
setup();
@@ -602,8 +743,7 @@ GeoMapView::GeoMapView(
add_child(&geopos);
map_opened = geomap.init();
if (!map_opened) return;
geomap.init();
setup();
add_child(&button_ok);
+57 -16
View File
@@ -29,16 +29,30 @@
#include "portapack.hpp"
#define MAX_MAP_ZOOM_IN 5
#define MAX_MAP_ZOOM_OUT 10
namespace ui {
#define MAX_MAP_ZOOM_IN 4000
#define MAX_MAP_ZOOM_OUT 10
#define MAP_ZOOM_RESOLUTION_LIMIT 5 // Max zoom-in to show map; rect height & width must divide into this evenly
#define INVALID_LAT_LON 200
#define INVALID_ANGLE 400
#define GEOMAP_BANNER_HEIGHT (3 * 16)
#define GEOMAP_RECT_WIDTH 240
#define GEOMAP_RECT_HEIGHT (320 - 16 - GEOMAP_BANNER_HEIGHT)
enum GeoMapMode {
DISPLAY,
PROMPT
};
struct GeoPoint {
public:
float x{0};
float y{0};
};
struct GeoMarker {
public:
float lat{0};
@@ -133,13 +147,15 @@ class GeoPos : public View {
2,
{0, 59},
1,
' '};
' ',
true};
NumberField field_lat_seconds{
{13 * 8, 1 * 16},
2,
{0, 59},
1,
' '};
' ',
true};
Text text_lat_decimal{
{17 * 8, 1 * 16, 13 * 8, 1 * 16},
""};
@@ -155,13 +171,15 @@ class GeoPos : public View {
2,
{0, 59},
1,
' '};
' ',
true};
NumberField field_lon_seconds{
{13 * 8, 2 * 16},
2,
{0, 59},
1,
' '};
' ',
true};
Text text_lon_decimal{
{17 * 8, 2 * 16, 13 * 8, 1 * 16},
""};
@@ -185,6 +203,9 @@ class GeoMap : public Widget {
bool on_encoder(const EncoderEvent delta) override;
bool on_keyboard(const KeyboardEvent event) override;
void update_my_position(float lat, float lon, int32_t altitude);
void update_my_orientation(uint16_t angle, bool refresh = false);
bool init();
void set_mode(GeoMapMode mode);
void set_manual_panning(bool v);
@@ -198,25 +219,40 @@ class GeoMap : public Widget {
angle_ = new_angle;
}
bool map_file_opened() { return map_opened; }
void set_hide_center_marker(bool hide) {
hide_center_marker_ = hide;
}
bool hide_center_marker() { return hide_center_marker_; }
static const int NumMarkerListElements = 30;
void clear_markers();
MapMarkerStored store_marker(GeoMarker& marker);
static const Dim banner_height = 3 * 16;
static const Dim geomap_rect_width = 240;
static const Dim geomap_rect_height = 320 - 16 - banner_height;
static const Dim banner_height = GEOMAP_BANNER_HEIGHT;
static const Dim geomap_rect_width = GEOMAP_RECT_WIDTH;
static const Dim geomap_rect_height = GEOMAP_RECT_HEIGHT;
private:
void draw_scale(Painter& painter);
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
ui::Point item_rect_pixel(GeoMarker& item);
GeoPoint lat_lon_to_map_pixel(float lat, float lon);
void draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
void draw_markers(Painter& painter);
void draw_mypos(Painter& painter);
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
void draw_map_grid();
void map_read_line(ui::Color* buffer, uint16_t pixels);
bool manual_panning_{false};
bool hide_center_marker_{false};
GeoMapMode mode_{};
File map_file{};
bool map_opened{};
bool map_visible{};
uint16_t map_width{}, map_height{};
int32_t map_center_x{}, map_center_y{};
int16_t map_zoom{1};
@@ -225,17 +261,22 @@ class GeoMap : public Widget {
double map_world_lon{};
double map_offset{};
int32_t x_pos{}, y_pos{};
int32_t prev_x_pos{0xFFFF}, prev_y_pos{0xFFFF};
float x_pos{}, y_pos{};
float prev_x_pos{32767.0f}, prev_y_pos{32767.0f};
float lat_{};
float lon_{};
float zoom_pixel_offset{0.0f};
float pixels_per_km{};
uint16_t angle_{};
std::string tag_{};
// the portapack's position data ( for example injected from serial )
GeoMarker my_pos{INVALID_LAT_LON, INVALID_LAT_LON, INVALID_ANGLE, ""}; // lat, lon, angle, tag
int32_t my_altitude{0};
int markerListLen{0};
GeoMarker markerList[NumMarkerListElements];
bool markerListUpdated{false};
bool redraw_map{false};
};
class GeoMapView : public View {
@@ -267,6 +308,8 @@ class GeoMapView : public View {
void focus() override;
void update_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed = 0);
void update_my_position(float lat, float lon, int32_t altitude);
void update_my_orientation(uint16_t angle, bool refresh = false);
std::string title() const override { return "Map view"; };
@@ -291,8 +334,6 @@ class GeoMapView : public View {
uint16_t angle_{};
std::function<void(void)> on_close_{nullptr};
bool map_opened{};
GeoPos geopos{
{0, 0},
altitude_unit_,
@@ -6,6 +6,44 @@ namespace ui {
/* static */ std::vector<DynamicBitmap<16, 16>> ExternalItemsMenuLoader::bitmaps;
// iterates over all ppma-s, and if it is runnable on the current system, it'll call the callback, and pass info.
/* static */ void ExternalItemsMenuLoader::load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback) {
if (!callback) return;
if (sd_card::status() != sd_card::Status::Mounted)
return;
for (const auto& entry : std::filesystem::directory_iterator(u"APPS", u"*.ppma")) {
auto filePath = u"/APPS/" + entry.path();
File app;
auto openError = app.open(filePath);
if (openError)
continue;
application_information_t application_information = {};
auto readResult = app.read(&application_information, sizeof(application_information_t));
if (!readResult)
continue;
if (application_information.header_version != CURRENT_HEADER_VERSION)
continue;
bool versionMatches = VERSION_MD5 == application_information.app_version;
if (!versionMatches) continue;
// here the app is startable and good.
std::string appshortname = filePath.filename().string();
if (appshortname.size() >= 5 && appshortname.substr(appshortname.size() - 5) == ".ppma") {
// Remove the ".ppma" suffix
appshortname = appshortname.substr(0, appshortname.size() - 5);
}
AppInfoConsole info{
.appCallName = appshortname.c_str(),
.appFriendlyName = reinterpret_cast<char*>(&application_information.app_name[0]),
.appLocation = application_information.menu_location};
callback(info);
}
}
/* static */ std::vector<GridItem> ExternalItemsMenuLoader::load_external_items(app_location_t app_location, NavigationView& nav) {
bitmaps.clear();
@@ -47,7 +85,9 @@ namespace ui {
bitmaps.push_back(std::move(dyn_bmp));
gridItem.on_select = [&nav, app_location, filePath]() {
run_external_app(nav, filePath);
if (!run_external_app(nav, filePath)) {
nav.display_modal("Error", "The .ppma file in your APPS\nfolder can't be read. Please\nupdate your SD Card content.");
}
};
} else {
gridItem.color = Color::light_grey();
@@ -65,19 +105,19 @@ namespace ui {
return external_apps;
}
/* static */ void ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) {
/* static */ bool ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) {
File app;
uint32_t checksum{0};
auto openError = app.open(filePath);
if (openError)
chDbgPanic("file gone");
return false;
application_information_t application_information = {};
auto readResult = app.read(&application_information, sizeof(application_information_t));
if (!readResult)
chDbgPanic("no data");
return false;
app.seek(0);
@@ -93,7 +133,9 @@ namespace ui {
readResult = app.read(&application_information.memory_location[file_read_index], bytes_to_read);
if (!readResult)
chDbgPanic("read error");
return false;
checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value());
if (readResult.value() < std::filesystem::max_file_block_size)
break;
@@ -114,7 +156,9 @@ namespace ui {
readResult = app.read(target_memory, bytes_to_read);
if (!readResult)
chDbgPanic("read error #2");
return false;
checksum += simple_checksum((uint32_t)target_memory, readResult.value());
if (readResult.value() != bytes_to_read)
break;
@@ -126,14 +170,20 @@ namespace ui {
readResult = app.read(&application_information.memory_location[file_read_index], bytes_to_read);
if (!readResult)
chDbgPanic("read error #3");
return false;
checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value());
if (readResult.value() < std::filesystem::max_file_block_size)
break;
}
}
if (checksum != EXT_APP_EXPECTED_CHECKSUM)
return false;
application_information.externalAppEntry(nav);
return true;
}
} // namespace ui
@@ -29,6 +29,8 @@
#include "file.hpp"
#define EXT_APP_EXPECTED_CHECKSUM 0x00000000
namespace ui {
template <size_t Width, size_t Height>
@@ -54,11 +56,11 @@ class ExternalItemsMenuLoader {
public:
static std::vector<GridItem> load_external_items(app_location_t, NavigationView&);
ExternalItemsMenuLoader() = delete;
static bool run_external_app(ui::NavigationView&, std::filesystem::path);
static void load_all_external_items_callback(std::function<void(AppInfoConsole&)> callback);
private:
static std::vector<DynamicBitmap<16, 16>> bitmaps;
static void run_external_app(ui::NavigationView&, std::filesystem::path);
};
} // namespace ui
+183 -101
View File
@@ -48,7 +48,7 @@
#include "ui_fsk_rx.hpp"
#include "ui_iq_trim.hpp"
// #include "ui_jammer.hpp" //moved to ext
// #include "ui_keyfob.hpp"
// #include "ui_keyfob.hpp" //moved to ext
// #include "ui_lcr.hpp"
#include "ui_level.hpp"
#include "ui_looking_glass_app.hpp"
@@ -111,6 +111,111 @@ namespace pmem = portapack::persistent_memory;
namespace ui {
bool CstrCmp::operator()(const char* a, const char* b) const {
return strcmp(a, b) < 0;
}
static NavigationView::AppMap generate_app_map(const NavigationView::AppList& appList) {
NavigationView::AppMap out;
for (auto& app : appList) {
if (app.id == nullptr) {
// Skip items with no id
continue;
}
auto res = out.emplace(app.id, app);
if (!res.second) {
chDbgPanic("Application cannot be added, ID not unique!");
}
}
return out;
}
// TODO(u-foka): Check consistency of command names (where we add rx/tx postfix)
const NavigationView::AppList NavigationView::appList = {
/* HOME ******************************************************************/
//{"playdead", "Play dead", HOME, Color::red(), &bitmap_icon_playdead, new ViewFactory<PlayDeadView>()},
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
{"remote", "Remote", HOME, ui::Color::green(), &bitmap_icon_remote, new ViewFactory<RemoteView>()},
{"scanner", "Scanner", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
{nullptr, "Debug", HOME, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
//{"about", "About", HOME, Color::cyan(), nullptr, new ViewFactory<AboutView>()},
/* RX ********************************************************************/
//{"acars", "ACARS", RX, Color::yellow(), &bitmap_icon_adsb, new ViewFactory<ACARSAppView>()},
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
//{"btle", "BTLE", RX, Color::yellow(), &bitmap_icon_btle, new ViewFactory<BTLERxView>()},
//{"blecomm", "BLE Comm", RX, ui::Color::orange(), &bitmap_icon_btle, new ViewFactory<BLECommView>()},
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
{"ert", "ERT Meter", RX, Color::green(), &bitmap_icon_ert, new ViewFactory<ERTAppView>()},
{"level", "Level", RX, Color::green(), &bitmap_icon_options_radio, new ViewFactory<LevelView>()},
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
{"tmps", "TPMS Cars", RX, Color::green(), &bitmap_icon_tpms, new ViewFactory<TPMSAppView>()},
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
//{"fskrx", "FSK RX", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<FskxRxMainView>()},
//{"dmr", "DMR", RX, Color::dark_grey(), &bitmap_icon_dmr, new ViewFactory<NotImplementedView>()},
//{"sigfox", "SIGFOX", RX, Color::dark_grey(), &bitmap_icon_fox, new ViewFactory<NotImplementedView>()},
//{"lora", "LoRa", RX, Color::dark_grey(), &bitmap_icon_lora, new ViewFactory<NotImplementedView>()},
//{"sstv", "SSTV", RX, Color::dark_grey(), &bitmap_icon_sstv, new ViewFactory<NotImplementedView>()},
//{"tetra", "TETRA", RX, Color::dark_grey(), &bitmap_icon_tetra, new ViewFactory<NotImplementedView>()},
/* TX ********************************************************************/
{"adsbtx", "ADS-B TX", TX, ui::Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBTxView>()},
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
{"bht", "BHT Xy/EP", TX, ui::Color::green(), &bitmap_icon_bht, new ViewFactory<BHTView>()},
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
{"morse", "Morse", TX, ui::Color::green(), &bitmap_icon_morse, new ViewFactory<MorseView>()},
//{"nuoptixdtmf", "Nuoptix DTMF", TX, ui::Color::green(), &bitmap_icon_nuoptix, new ViewFactory<NuoptixView>()},
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
{"pocsagtx", "POCSAG TX", TX, ui::Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGTXView>()},
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
{"sstvtx", "SSTV", TX, ui::Color::green(), &bitmap_icon_sstv, new ViewFactory<SSTVTXView>()},
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
/* UTILITIES *************************************************************/
{"antennalength", "Antenna Length", UTILITIES, Color::green(), &bitmap_icon_tools_antenna, new ViewFactory<WhipCalcView>()},
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
//{"tonesearch", "Tone Search", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<ToneSearchView>()},
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
// Dangerous apps.
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_temperature, new ViewFactory<FlashUtilityView>()},
{nullptr, "Wipe SD card", UTILITIES, Color::red(), &bitmap_icon_tools_wipesd, new ViewFactory<WipeSDView>()},
};
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
bool NavigationView::StartAppByName(const char* name) {
home(false);
auto it = appMap.find(name);
if (it != appMap.end()) {
push_view(std::unique_ptr<View>(it->second.viewFactory->produce(*this)));
return true;
}
return false;
}
/* StatusTray ************************************************************/
StatusTray::StatusTray(Point pos)
@@ -398,8 +503,8 @@ void SystemStatusView::rtc_battery_workaround() {
month = (timestamp.FAT_date >> 5) & 0xF;
day = timestamp.FAT_date & 0x1F;
// bump to next month at 28 days for simplicity
if (++day > 28) {
// bump to next month
if (++day > rtc_time::days_per_month(year, month)) {
day = 1;
if (++month > 12) {
month = 1;
@@ -407,7 +512,7 @@ void SystemStatusView::rtc_battery_workaround() {
}
}
} else {
make_new_directory(SETTINGS_DIR);
ensure_directory(SETTINGS_DIR);
make_new_file(DATE_FILEFLAG);
year = 1980;
@@ -442,16 +547,16 @@ InformationView::InformationView(
&ltime});
#if GCC_VERSION_MISMATCH
static constexpr Style style_gcc_warning{
.font = font::fixed_8x16,
.background = {33, 33, 33},
.foreground = Color::yellow(),
};
version.set_style(&style_gcc_warning);
version.set_style(&Styles::yellow);
#else
version.set_style(&style_infobar);
#endif
if (firmware_checksum_error()) {
version.set("FLASH ERROR");
version.set_style(&Styles::red);
}
ltime.set_style(&style_infobar);
refresh();
set_dirty();
@@ -463,6 +568,17 @@ void InformationView::refresh() {
ltime.set_date_enabled(pmem::clock_with_date());
}
bool InformationView::firmware_checksum_error() {
static bool fw_checksum_checked{false};
static bool fw_checksum_error{false};
// only checking firmware checksum once per boot
if (!fw_checksum_checked) {
fw_checksum_error = (simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE) != FLASH_EXPECTED_CHECKSUM);
}
return fw_checksum_error;
}
/* Navigation ************************************************************/
bool NavigationView::is_top() const {
@@ -497,6 +613,14 @@ void NavigationView::pop(bool trigger_update) {
if (on_pop) on_pop();
}
void NavigationView::home(bool trigger_update) {
while (view_stack.size() > 1) {
pop(false);
}
if (trigger_update) update_view();
}
void NavigationView::display_modal(
const std::string& title,
const std::string& message) {
@@ -556,43 +680,47 @@ bool NavigationView::set_on_pop(std::function<void()> on_pop) {
return true;
}
/* Helpers **************************************************************/
static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
for (auto& app : NavigationView::appList) {
if (app.menuLocation == loc) {
grid.add_item({app.displayName, app.iconColor, app.icon,
[&nav, &app]() { nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }});
}
};
}
void addExternalItems(NavigationView& nav, app_location_t location, BtnGridView& grid) {
auto externalItems = ExternalItemsMenuLoader::load_external_items(location, nav);
if (externalItems.empty()) {
grid.add_item({"Notice",
Color::red(),
&bitmap_icon_debug,
[&nav]() {
nav.display_modal(
"Notice",
"External app directory empty;\n"
"see Mayhem wiki and copy apps\n"
"to APPS folder of SD card.");
}});
} else {
for (auto const& gridItem : externalItems) {
grid.add_item(gridItem);
}
}
}
/* ReceiversMenuView *****************************************************/
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
if (pmem::show_gui_return_icon()) {
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
add_item({"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }});
}
add_items({
// {"ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }},
{"ADS-B", Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBRxView>(); }},
{"AIS Boats", Color::green(), &bitmap_icon_ais, [&nav]() { nav.push<AISAppView>(); }},
//{"Analog TV", Color::yellow(), &bitmap_icon_sstv, [&nav]() { nav.push<AnalogTvView>(); }}, //moved to ext
{"APRS", Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSRXView>(); }},
{"Audio", Color::green(), &bitmap_icon_speaker, [&nav]() { nav.push<AnalogAudioView>(); }},
//{"BTLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push<BTLERxView>(); }},
//{"BLE Comm", ui::Color::orange(), &bitmap_icon_btle, [&nav]() { nav.push<BLECommView>(); }},
{"BLE Rx", Color::green(), &bitmap_icon_btle, [&nav]() { nav.push<BLERxView>(); }},
{"ERT Meter", Color::green(), &bitmap_icon_ert, [&nav]() { nav.push<ERTAppView>(); }},
{"Level", Color::green(), &bitmap_icon_options_radio, [&nav]() { nav.push<LevelView>(); }},
//{"NRF", Color::yellow(), &bitmap_icon_nrf, [&nav]() { nav.push<NRFRxView>(); }}, //moved to ext
{"POCSAG", Color::green(), &bitmap_icon_pocsag, [&nav]() { nav.push<POCSAGAppView>(); }},
{"Radiosnde", Color::green(), &bitmap_icon_sonde, [&nav]() { nav.push<SondeView>(); }},
{"Recon", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ReconView>(); }},
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
{"TPMS Cars", Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push<TPMSAppView>(); }},
{"Weather", Color::green(), &bitmap_icon_thermometer, [&nav]() { nav.push<WeatherView>(); }},
{"SubGhzD", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<SubGhzDView>(); }},
// {"FSK RX", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<FskxRxMainView>(); }},
// {"DMR", Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); }},
// {"SIGFOX", Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); }},
// {"LoRa", Color::dark_grey(), &bitmap_icon_lora, [&nav](){ nav.push<NotImplementedView>(); }},
// {"SSTV", Color::dark_grey(), &bitmap_icon_sstv, [&nav](){ nav.push<NotImplementedView>(); }},
// {"TETRA", Color::dark_grey(), &bitmap_icon_tetra, [&nav](){ nav.push<NotImplementedView>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::RX, nav)) {
add_item(gridItem);
};
add_apps(nav, *this, RX);
addExternalItems(nav, app_location_t::RX, *this);
}
/* TransmittersMenuView **************************************************/
@@ -601,31 +729,10 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
if (pmem::show_gui_return_icon()) {
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({
{"ADS-B TX", ui::Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBTxView>(); }},
{"APRS TX", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSTXView>(); }},
{"BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav]() { nav.push<BHTView>(); }},
{"BLE Tx", ui::Color::green(), &bitmap_icon_btle, [&nav]() { nav.push<BLETxView>(); }},
// {"BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav]() { nav.push<CoasterPagerView>(); }}, //moved to ext
//{"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }}, //moved to ext
//{"Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav]() { nav.push<JammerView>(); }}, //moved to ext
// { "Key fob", ui::Color::orange(), &bitmap_icon_keyfob, [&nav](){ nav.push<KeyfobView>(); }},
// {"LGE", ui::Color::yellow(), &bitmap_icon_lge, [&nav]() { nav.push<LGEView>(); }}, //moved to ext
{"Morse", ui::Color::green(), &bitmap_icon_morse, [&nav]() { nav.push<MorseView>(); }},
// { "Nuoptix DTMF", ui::Color::green(), &bitmap_icon_nuoptix, [&nav](){ nav.push<NuoptixView>(); }},
{"OOK", ui::Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<EncodersView>(); }},
{"POCSAG TX", ui::Color::green(), &bitmap_icon_pocsag, [&nav]() { nav.push<POCSAGTXView>(); }},
{"RDS", ui::Color::green(), &bitmap_icon_rds, [&nav]() { nav.push<RDSView>(); }},
{"Soundbrd", ui::Color::green(), &bitmap_icon_soundboard, [&nav]() { nav.push<SoundBoardView>(); }},
//{"S.Painter", ui::Color::orange(), &bitmap_icon_paint, [&nav]() { nav.push<SpectrumPainterView>(); }},
{"SSTV", ui::Color::green(), &bitmap_icon_sstv, [&nav]() { nav.push<SSTVTXView>(); }},
// {"TEDI/LCR", ui::Color::yellow(), &bitmap_icon_lcr, [&nav]() { nav.push<LCRView>(); }}, //moved to ext
{"TouchTune", ui::Color::green(), &bitmap_icon_touchtunes, [&nav]() { nav.push<TouchTunesView>(); }},
});
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::TX, nav)) {
add_item(gridItem);
};
add_apps(nav, *this, TX);
addExternalItems(nav, app_location_t::TX, *this);
}
/* UtilitiesMenuView *****************************************************/
@@ -634,26 +741,10 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
if (pmem::show_gui_return_icon()) {
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
}
add_items({
{"Antenna Length", Color::green(), &bitmap_icon_tools_antenna, [&nav]() { nav.push<WhipCalcView>(); }},
{"File Manager", Color::green(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
{"Freq. Manager", Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
{"Notepad", Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
{"IQ Trim", Color::orange(), &bitmap_icon_trim, [&nav]() { nav.push<IQTrimView>(); }},
{"SD Over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
{"Signal Gen", Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
// {"Test App", Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); }},
// {"Tone Search", Color::dark_grey(), nullptr, [&nav](){ nav.push<ToneSearchView>(); }},
{"Wav View", Color::yellow(), &bitmap_icon_soundboard, [&nav]() { nav.push<ViewWavView>(); }},
// Dangerous apps.
{"Flash Utility", Color::red(), &bitmap_icon_temperature, [&nav]() { nav.push<FlashUtilityView>(); }},
{"Wipe SD card", Color::red(), &bitmap_icon_tools_wipesd, [&nav]() { nav.push<WipeSDView>(); }},
});
add_apps(nav, *this, UTILITIES);
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::UTILITIES, nav)) {
add_item(gridItem);
};
addExternalItems(nav, app_location_t::UTILITIES, *this);
set_max_rows(2); // allow wider buttons
}
@@ -673,22 +764,9 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) {
}
SystemMenuView::SystemMenuView(NavigationView& nav) {
add_items({
// {"Play dead", Color::red(), &bitmap_icon_playdead, [&nav]() { nav.push<PlayDeadView>(); }},
{"Receive", Color::cyan(), &bitmap_icon_receivers, [&nav]() { nav.push<ReceiversMenuView>(); }},
{"Transmit", Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push<TransmittersMenuView>(); }},
{"Capture", Color::red(), &bitmap_icon_capture, [&nav]() { nav.push<CaptureAppView>(); }},
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<PlaylistView>(); }},
{"Remote", ui::Color::green(), &bitmap_icon_remote, [&nav]() { nav.push<RemoteView>(); }},
{"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
{"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},
{"Looking Glass", Color::green(), &bitmap_icon_looking, [&nav]() { nav.push<GlassView>(); }},
{"Utilities", Color::cyan(), &bitmap_icon_utilities, [&nav]() { nav.push<UtilitiesMenuView>(); }},
{"Settings", Color::cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SettingsMenuView>(); }},
{"Debug", Color::light_grey(), &bitmap_icon_debug, [&nav]() { nav.push<DebugMenuView>(); }},
{"HackRF", Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }},
// {"About", Color::cyan(), nullptr, [&nav]() { nav.push<AboutView>(); }},
});
add_apps(nav, *this, HOME);
add_item({"HackRF", Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }});
set_max_rows(2); // allow wider buttons
set_arrow_enabled(false);
@@ -751,6 +829,9 @@ SystemView::SystemView(
Context& SystemView::context() const {
return context_;
}
NavigationView* SystemView::get_navigation_view() {
return &navigation_view;
}
void SystemView::toggle_overlay() {
switch (++overlay_active) {
@@ -758,7 +839,7 @@ void SystemView::toggle_overlay() {
this->add_child(&this->overlay);
this->set_dirty();
shared_memory.request_m4_performance_counter = 1;
shared_memory.m4_cpu_usage = 0;
shared_memory.m4_performance_counter = 0;
shared_memory.m4_heap_usage = 0;
shared_memory.m4_stack_usage = 0;
break;
@@ -766,11 +847,12 @@ void SystemView::toggle_overlay() {
this->remove_child(&this->overlay);
this->add_child(&this->overlay2);
this->set_dirty();
shared_memory.request_m4_performance_counter = 0;
shared_memory.request_m4_performance_counter = 2;
break;
case 3:
this->remove_child(&this->overlay2);
this->set_dirty();
shared_memory.request_m4_performance_counter = 0;
overlay_active = 0;
break;
}
+40 -3
View File
@@ -23,6 +23,10 @@
#ifndef __UI_NAVIGATION_H__
#define __UI_NAVIGATION_H__
#include <vector>
#include <map>
#include <utility>
#include "ui.hpp"
#include "ui_widget.hpp"
#include "ui_focus.hpp"
@@ -40,9 +44,8 @@
#include "diskio.h"
#include "lfsr_random.hpp"
#include "sd_card.hpp"
#include <vector>
#include <utility>
#include "external_app.hpp"
#include "view_factory.hpp"
// for incrementing fake date when RTC battery is dead
#define DATE_FILEFLAG u"/SETTINGS/DATE_FILEFLAG"
@@ -57,6 +60,28 @@ enum modal_t {
ABORT
};
class CstrCmp {
public:
bool operator()(const char* a, const char* b) const;
};
// Should only be used as part of the appList in NavigationView, the viewFactory will never be destroyed.
class AppInfo {
public:
const char* id; // MUST be unique! Used by serial command to start the app so it also has to make sense
const char* displayName;
app_location_t menuLocation;
Color iconColor;
const Bitmap* icon;
ViewFactoryBase* viewFactory; // Never destroyed, and I believe it's ok ;) Having a unique_ptr here breaks the initializer list of appList
};
struct AppInfoConsole {
const char* appCallName;
const char* appFriendlyName;
const app_location_t appLocation;
};
class NavigationView : public View {
public:
std::function<void(const View&)> on_view_changed{};
@@ -85,6 +110,7 @@ class NavigationView : public View {
View* push_view(std::unique_ptr<View> new_view);
void replace(View* v);
void pop(bool trigger_update = true);
void home(bool trigger_update);
void display_modal(const std::string& title, const std::string& message);
void display_modal(
@@ -99,6 +125,14 @@ class NavigationView : public View {
* Returns true if the handler was bound successfully. */
bool set_on_pop(std::function<void()> on_pop);
// App list is used to preserve order, so the menu items in the menu grid can stay in place
// App map is used to look up apps by id used by serial app start
using AppMap = std::map<const char*, const AppInfo&, CstrCmp>;
using AppList = std::vector<AppInfo>;
static const AppMap appMap;
static const AppList appList;
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
private:
struct ViewState {
std::unique_ptr<View> view;
@@ -252,6 +286,7 @@ class InformationView : public View {
public:
InformationView(NavigationView& nav);
void refresh();
bool firmware_checksum_error();
private:
// static constexpr auto version_string = "v1.4.4"; // This is commented out as we are now setting the version via ENV (VERSION_STRING=v1.0.0)
@@ -321,6 +356,8 @@ class SystemView : public View {
void toggle_overlay();
void paint_overlay();
NavigationView* get_navigation_view();
private:
uint8_t overlay_active{0};
+1 -1
View File
@@ -181,7 +181,7 @@ void RecordView::start() {
std::filesystem::path base_path;
if (filename_date_frequency) {
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
// ISO 8601
std::string date_time =
+8 -2
View File
@@ -1,11 +1,12 @@
extern "C" {
#include "usb_serial_io.h"
#include "usb_serial_device_to_host.h"
#include "usb_serial_cdc.h"
}
#include "usb_serial_shell.hpp"
#include "usb_serial.hpp"
#include "portapack.hpp"
#include "usb_serial_host_to_device.hpp"
#include <libopencm3/cm3/common.h>
#include <libopencm3/lpc43xx/usb.h>
@@ -24,6 +25,7 @@ void USBSerial::initialize() {
init_serial_usb_driver(&SUSBD1);
shellInit();
init_host_to_device();
}
void USBSerial::dispatch() {
@@ -35,7 +37,11 @@ void USBSerial::dispatch() {
create_shell(_eventDispatcher);
}
bulk_out_receive();
schedule_host_to_device_transfer();
}
void USBSerial::dispatch_transfer() {
complete_host_to_device_transfer();
}
void USBSerial::on_channel_opened() {
+1
View File
@@ -32,6 +32,7 @@ class USBSerial {
public:
void initialize();
void dispatch();
void dispatch_transfer();
void on_channel_opened();
void on_channel_closed();
void setEventDispatcher(EventDispatcher* ed) { _eventDispatcher = ed; }
+16
View File
@@ -23,10 +23,25 @@
#include "usb_serial_endpoints.h"
#include "usb_serial_event.hpp"
uint32_t EVT_MASK_USB = EVENT_MASK(8);
extern void usb0_isr(void);
static Thread* thread_usb_event = NULL;
CH_IRQ_HANDLER(USB0_IRQHandler) {
CH_IRQ_PROLOGUE();
const uint32_t status = USB0_USBSTS_D & USB0_USBINTR_D;
usb0_isr();
if (status & USB0_USBSTS_D_UI) {
chSysLockFromIsr();
chEvtSignalI(thread_usb_event, EVT_MASK_USB);
chSysUnlockFromIsr();
}
CH_IRQ_EPILOGUE();
}
@@ -46,6 +61,7 @@ uint32_t __strex(uint32_t val, volatile uint32_t* addr) {
void nvic_enable_irq(uint8_t irqn) {
NVIC_ISER(irqn / 32) = (1 << (irqn % 32));
thread_usb_event = chThdSelf();
}
void usb_configuration_changed(usb_device_t* const device) {
+3 -3
View File
@@ -118,7 +118,7 @@ uint8_t usb_descriptor_configuration_full_speed[] = {
USB_INT_IN_EP_ADDR, // bEndpointAddress
0x03, // bmAttributes: BULK
USB_WORD(16), // wMaxPacketSize
0xFF, // bInterval: no NAK
0x20, // bInterval: no NAK
9, // bLength
USB_DESCRIPTOR_TYPE_INTERFACE, // bDescriptorType
@@ -203,7 +203,7 @@ uint8_t usb_descriptor_configuration_high_speed[] = {
USB_INT_IN_EP_ADDR, // bEndpointAddress
0x03, // bmAttributes: BULK
USB_WORD(16), // wMaxPacketSize
0xFF, // bInterval: no NAK
0x20, // bInterval: no NAK
9, // bLength
USB_DESCRIPTOR_TYPE_INTERFACE, // bDescriptorType
@@ -264,7 +264,7 @@ uint8_t usb_descriptor_string_manufacturer[] = {
};
uint8_t usb_descriptor_string_product[] = {
43, // bLength
34, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'P', 0x00,
'o', 0x00,
@@ -19,7 +19,7 @@
* Boston, MA 02110-1301, USA.
*/
#include "usb_serial_io.h"
#include "usb_serial_device_to_host.h"
#include "usb_serial_endpoints.h"
@@ -40,42 +40,11 @@
SerialUSBDriver SUSBD1;
void bulk_out_receive(void) {
int ret;
do {
ret = usb_transfer_schedule(
&usb_endpoint_bulk_out,
&usb_endpoint_bulk_out.buffer[0],
32,
serial_bulk_transfer_complete,
NULL);
} while (ret != -1);
}
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) {
(void)user_data;
chSysLockFromIsr();
for (unsigned int i = 0; i < bytes_transferred; i++) {
msg_t ret;
do {
ret = chIQPutI(&SUSBD1.iqueue, usb_endpoint_bulk_out.buffer[i]);
if (ret == Q_FULL)
chThdSleepMilliseconds(1);
} while (ret == Q_FULL);
}
chSysUnlockFromIsr();
}
static void onotify(GenericQueue* qp) {
SerialUSBDriver* sdp = chQGetLink(qp);
uint8_t buff[64];
uint8_t buff[USBSERIAL_BUFFERS_SIZE];
int n = chOQGetFullI(&sdp->oqueue);
if (n > 64) n = 64; // don't overflow
if (n > USBSERIAL_BUFFERS_SIZE) n = USBSERIAL_BUFFERS_SIZE; // don't overflow
if (n > 0) {
for (int i = 0; i < n; i++) {
buff[i] = chOQGetI(&sdp->oqueue);
@@ -139,6 +108,45 @@ static const struct SerialUSBDriverVMT vmt = {
void init_serial_usb_driver(SerialUSBDriver* sdp) {
sdp->vmt = &vmt;
chIQInit(&sdp->iqueue, sdp->ib, SERIAL_BUFFERS_SIZE, NULL, sdp);
chOQInit(&sdp->oqueue, sdp->ob, SERIAL_BUFFERS_SIZE, onotify, sdp);
chIQInit(&sdp->iqueue, sdp->ib, USBSERIAL_BUFFERS_SIZE, NULL, sdp);
chOQInit(&sdp->oqueue, sdp->ob, USBSERIAL_BUFFERS_SIZE, onotify, sdp);
}
// queue handler from ch
static msg_t qwait(GenericQueue* qp, systime_t time) {
if (TIME_IMMEDIATE == time)
return Q_TIMEOUT;
currp->p_u.wtobjp = qp;
queue_insert(currp, &qp->q_waiting);
return chSchGoSleepTimeoutS(THD_STATE_WTQUEUE, time);
}
// This function fills the output buffer, and sends all data in 1 packet
size_t fillOBuffer(OutputQueue* oqp, const uint8_t* bp, size_t n) {
qnotify_t nfy = oqp->q_notify;
size_t w = 0;
chDbgCheck(n > 0, "chOQWriteTimeout");
chSysLock();
while (TRUE) {
while (chOQIsFullI(oqp)) {
if (qwait((GenericQueue*)oqp, TIME_INFINITE) != Q_OK) {
chSysUnlock();
return w;
}
}
while (!chOQIsFullI(oqp) && n > 0) {
oqp->q_counter--;
*oqp->q_wrptr++ = *bp++;
if (oqp->q_wrptr >= oqp->q_top)
oqp->q_wrptr = oqp->q_buffer;
w++;
--n;
}
if (nfy) nfy(oqp);
chSysUnlock(); /* Gives a preemption chance in a controlled point.*/
if (n == 0) return w;
chSysLock();
}
}
@@ -24,6 +24,12 @@
#include "ch.h"
#include "hal.h"
#define USB_BULK_BUFFER_SIZE 64
#ifndef USBSERIAL_BUFFERS_SIZE
#define USBSERIAL_BUFFERS_SIZE 128
#endif
struct SerialUSBDriverVMT {
_base_asynchronous_channel_methods
};
@@ -31,10 +37,10 @@ struct SerialUSBDriverVMT {
struct SerialUSBDriver {
/** @brief Virtual Methods Table.*/
const struct SerialUSBDriverVMT* vmt;
InputQueue iqueue; /* Output queue.*/
OutputQueue oqueue; /* Input circular buffer.*/
uint8_t ib[SERIAL_BUFFERS_SIZE]; /* Output circular buffer.*/
uint8_t ob[SERIAL_BUFFERS_SIZE];
InputQueue iqueue; /* Output queue.*/
OutputQueue oqueue; /* Input circular buffer.*/
uint8_t ib[USBSERIAL_BUFFERS_SIZE]; /* Output circular buffer.*/
uint8_t ob[USBSERIAL_BUFFERS_SIZE];
};
typedef struct SerialUSBDriver SerialUSBDriver;
@@ -42,5 +48,11 @@ typedef struct SerialUSBDriver SerialUSBDriver;
extern SerialUSBDriver SUSBD1;
void init_serial_usb_driver(SerialUSBDriver* sdp);
void bulk_out_receive(void);
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
#ifdef __cplusplus
extern "C" {
#endif
size_t fillOBuffer(OutputQueue* oqp, const uint8_t* bp, size_t n);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "usb_serial_host_to_device.hpp"
#include "event_m0.hpp"
#include "usb_serial_device_to_host.h"
extern "C" {
#include <common/usb.h>
#include <hackrf_usb/usb_device.h>
#include <hackrf_usb/usb_endpoint.h>
}
#include <queue>
#include <vector>
static Thread* thread_usb_event = NULL;
struct usb_bulk_buffer_t {
uint8_t* data;
size_t length;
bool completed;
};
std::queue<usb_bulk_buffer_t*> usb_bulk_buffer_queue;
std::queue<usb_bulk_buffer_t*> usb_bulk_buffer_spare;
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) {
usb_bulk_buffer_t* transfer_data = (usb_bulk_buffer_t*)user_data;
transfer_data->length = bytes_transferred;
transfer_data->completed = true;
}
void init_host_to_device() {
thread_usb_event = chThdSelf();
}
void schedule_host_to_device_transfer() {
if (usb_bulk_buffer_queue.size() >= 8)
return;
static usb_bulk_buffer_t* transfer_data = nullptr;
int ret;
do {
if (transfer_data == nullptr) {
if (usb_bulk_buffer_spare.empty() == false) {
transfer_data = usb_bulk_buffer_spare.front();
transfer_data->length = 0;
transfer_data->completed = false;
usb_bulk_buffer_spare.pop();
} else {
transfer_data = new usb_bulk_buffer_t{
.data = new uint8_t[USB_BULK_BUFFER_SIZE],
.length = 0,
.completed = false};
}
}
ret = usb_transfer_schedule(
&usb_endpoint_bulk_out,
transfer_data->data,
USB_BULK_BUFFER_SIZE,
serial_bulk_transfer_complete,
transfer_data);
if (ret != -1) {
usb_bulk_buffer_queue.push(transfer_data);
transfer_data = nullptr;
if (usb_bulk_buffer_queue.size() >= 8)
return;
}
} while (ret != -1);
}
void complete_host_to_device_transfer() {
for (; !usb_bulk_buffer_queue.empty(); usb_bulk_buffer_queue.pop()) {
usb_bulk_buffer_t* transfer_data = usb_bulk_buffer_queue.front();
while (transfer_data->completed == false)
return;
chSysLock();
for (unsigned int i = 0; i < transfer_data->length; i++) {
msg_t ret;
do {
ret = chIQPutI(&SUSBD1.iqueue, transfer_data->data[i]);
if (ret == Q_FULL) {
chSysUnlock();
chThdSleepMilliseconds(1); // wait for shell thread when buffer is full
chSysLock();
}
} while (ret == Q_FULL);
}
chSysUnlock();
usb_bulk_buffer_spare.push(transfer_data);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "ch.h"
#include "hal.h"
#define USB_BULK_BUFFER_SIZE 64
void init_host_to_device();
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
void schedule_host_to_device_transfer();
void complete_host_to_device_transfer();
+293 -247
View File
@@ -35,18 +35,23 @@
#include "portapack_cpld_data.hpp"
#include "crc.hpp"
#include "hackrf_cpld_data.hpp"
#include "performance_counter.hpp"
#include "usb_serial_io.h"
#include "ff.h"
#include "usb_serial_device_to_host.h"
#include "chprintf.h"
#include "chqueues.h"
#include "ui_external_items_menu_loader.hpp"
#include "ui_flash_utility.hpp"
#include "untar.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "usb_serial_shell_filesystem.hpp"
#include "portapack_persistent_memory.hpp"
#include <string>
#include <codecvt>
#include <cstring>
#include <locale>
#include <libopencm3/lpc43xx/wwdt.h>
#define SHELL_WA_SIZE THD_WA_SIZE(1024 * 3)
@@ -57,45 +62,6 @@ static EventDispatcher* getEventDispatcherInstance() {
return _eventDispatcherInstance;
}
// queue handler from ch
static msg_t qwait(GenericQueue* qp, systime_t time) {
if (TIME_IMMEDIATE == time)
return Q_TIMEOUT;
currp->p_u.wtobjp = qp;
queue_insert(currp, &qp->q_waiting);
return chSchGoSleepTimeoutS(THD_STATE_WTQUEUE, time);
}
// This function fills the output buffer, and sends all data in 1 packet
static size_t fillOBuffer(OutputQueue* oqp, const uint8_t* bp, size_t n) {
qnotify_t nfy = oqp->q_notify;
size_t w = 0;
chDbgCheck(n > 0, "chOQWriteTimeout");
chSysLock();
while (TRUE) {
while (chOQIsFullI(oqp)) {
if (qwait((GenericQueue*)oqp, TIME_INFINITE) != Q_OK) {
chSysUnlock();
return w;
}
}
while (!chOQIsFullI(oqp) && n > 0) {
oqp->q_counter--;
*oqp->q_wrptr++ = *bp++;
if (oqp->q_wrptr >= oqp->q_top)
oqp->q_wrptr = oqp->q_buffer;
w++;
--n;
}
if (nfy) nfy(oqp);
chSysUnlock(); /* Gives a preemption chance in a controlled point.*/
if (n == 0) return w;
chSysLock();
}
}
static void cmd_reboot(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)chp;
(void)argc;
@@ -158,11 +124,6 @@ static void cmd_sd_over_usb(BaseSequentialStream* chp, int argc, char* argv[]) {
m0_halt();
}
std::filesystem::path path_from_string8(char* path) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
return conv.from_bytes(path);
}
bool strEndsWith(const std::u16string& str, const std::u16string& suffix) {
if (str.length() >= suffix.length()) {
std::u16string endOfString = str.substr(str.length() - suffix.length());
@@ -185,6 +146,13 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
return;
}
auto evtd = getEventDispatcherInstance();
if (!evtd) return;
auto top_widget = evtd->getTopWidget();
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
nav->display_modal("Flashing", "Flashing from serial.\r\nPlease wait!\r\nDevice will restart.");
// check file extensions
if (strEndsWith(path.native(), u".ppfw.tar")) {
// extract tar
@@ -196,15 +164,24 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
});
if (res.empty()) {
chprintf(chp, "error bad TAR file.\r\n");
nav->pop();
return;
}
path = res; // it will contain the last bin file in tar
} else if (strEndsWith(path.native(), u".bin")) {
// nothing to do for this case yet.
} else {
chprintf(chp, "error only .bin or .ppfw.tar files canbe flashed.\r\n");
chprintf(chp, "error only .bin or .ppfw.tar files can be flashed.\r\n");
nav->pop();
return;
}
if (!ui::valid_firmware_file(path.native().c_str())) {
chprintf(chp, "error corrupt firmware file.\r\n");
nav->pop();
return;
}
chprintf(chp, "Flashing: ");
chprintf(chp, path.string().c_str());
chprintf(chp, "\r\n");
@@ -245,6 +222,9 @@ static void cmd_screenframe(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argc;
(void)argv;
auto evtd = getEventDispatcherInstance();
evtd->enter_shell_working_mode();
for (int i = 0; i < ui::screen_height; i++) {
std::array<ui::ColorRGB888, ui::screen_width> row;
portapack::display.read_pixels({0, i, ui::screen_width, 1}, row);
@@ -255,6 +235,9 @@ static void cmd_screenframe(BaseSequentialStream* chp, int argc, char* argv[]) {
}
chprintf(chp, "\r\n");
}
evtd->exit_shell_working_mode();
chprintf(chp, "ok\r\n");
}
@@ -272,21 +255,25 @@ static void cmd_screenframeshort(BaseSequentialStream* chp, int argc, char* argv
(void)argc;
(void)argv;
auto evtd = getEventDispatcherInstance();
evtd->enter_shell_working_mode();
for (int y = 0; y < ui::screen_height; y++) {
std::array<ui::ColorRGB888, ui::screen_width> row;
portapack::display.read_pixels({0, y, ui::screen_width, 1}, row);
for (int px = 0; px < ui::screen_width; px += 60) {
char buffer[60];
for (int i = 0; i < 60; ++i) {
buffer[i] = getChrFromRgb(row[px + i].r, row[px + i].g, row[px + i].b);
}
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)buffer, 60);
char buffer[242];
for (int i = 0; i < 240; ++i) {
buffer[i] = getChrFromRgb(row[i].r, row[i].g, row[i].b);
}
chprintf(chp, "\r\n");
buffer[240] = '\r';
buffer[241] = '\n';
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)buffer, 242);
}
chprintf(chp, "ok\r\n");
evtd->exit_shell_working_mode();
chprintf(chp, "\r\nok\r\n");
}
static void cmd_write_memory(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 2) {
chprintf(chp, "usage: write_memory <address> <value (1 or 4 bytes)>\r\n");
@@ -341,6 +328,11 @@ static void cmd_button(BaseSequentialStream* chp, int argc, char* argv[]) {
control::debug::inject_switch(button);
// Wait two frame syncs to ensure action has painted
auto evtd = getEventDispatcherInstance();
evtd->wait_finish_frame();
evtd->wait_finish_frame();
chprintf(chp, "ok\r\n");
}
@@ -363,6 +355,11 @@ static void cmd_touch(BaseSequentialStream* chp, int argc, char* argv[]) {
}
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::Start});
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::End});
// Wait two frame syncs to ensure action has painted
evtd->wait_finish_frame();
evtd->wait_finish_frame();
chprintf(chp, "ok\r\n");
}
@@ -401,185 +398,9 @@ static void cmd_keyboard(BaseSequentialStream* chp, int argc, char* argv[]) {
evtd->emulateKeyboard(chr);
}
chprintf(chp, "ok\r\n");
}
static void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: ls /\r\n");
return;
}
auto path = path_from_string8(argv[0]);
for (const auto& entry : std::filesystem::directory_iterator(path, "*")) {
if (std::filesystem::is_directory(entry.status())) {
chprintf(chp, "%s/\r\n", entry.path().string().c_str());
} else if (std::filesystem::is_regular_file(entry.status())) {
chprintf(chp, "%s\r\n", entry.path().string().c_str());
} else {
chprintf(chp, "%s *\r\n", entry.path().string().c_str());
}
}
}
static void cmd_sd_delete(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: rm <path>\r\n");
return;
}
auto path = path_from_string8(argv[0]);
if (!std::filesystem::file_exists(path)) {
chprintf(chp, "file not found.\r\n");
return;
}
delete_file(path);
chprintf(chp, "ok\r\n");
}
File* shell_file = nullptr;
static void cmd_sd_filesize(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: filesize <path>\r\n");
return;
}
auto path = path_from_string8(argv[0]);
FILINFO res;
auto stat = f_stat(path.tchar(), &res);
if (stat == FR_OK) {
chprintf(chp, "%lu\r\n", res.fsize);
chprintf(chp, "ok\r\n");
} else {
chprintf(chp, "error\r\n");
}
}
static void cmd_sd_open(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: open <path>\r\n");
return;
}
if (shell_file != nullptr) {
chprintf(chp, "file already open\r\n");
return;
}
auto path = path_from_string8(argv[0]);
shell_file = new File();
shell_file->open(path, false, true);
chprintf(chp, "ok\r\n");
}
static void cmd_sd_seek(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: seek <offset>\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
int address = (int)strtol(argv[0], NULL, 10);
shell_file->seek(address);
chprintf(chp, "ok\r\n");
}
static void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argv;
if (argc != 0) {
chprintf(chp, "usage: close\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
delete shell_file;
shell_file = nullptr;
chprintf(chp, "ok\r\n");
}
static void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: read <number of bytes>\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
int size = (int)strtol(argv[0], NULL, 10);
uint8_t buffer[62];
do {
File::Size bytes_to_read = size > 62 ? 62 : size;
auto bytes_read = shell_file->read(buffer, bytes_to_read);
if (bytes_read.is_error()) {
chprintf(chp, "error %d\r\n", bytes_read.error());
return;
}
std::string res = to_string_hex_array(buffer, bytes_read.value());
res += "\r\n";
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)res.c_str(), res.size());
if (bytes_to_read != bytes_read.value())
return;
size -= bytes_to_read;
} while (size > 0);
chprintf(chp, "ok\r\n");
}
static void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: write 0123456789ABCDEF\r\n";
if (argc != 1) {
chprintf(chp, usage);
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
size_t data_string_len = strlen(argv[0]);
if (data_string_len % 2 != 0) {
chprintf(chp, usage);
return;
}
for (size_t i = 0; i < data_string_len; i++) {
char c = argv[0][i];
if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
chprintf(chp, usage);
return;
}
}
char buffer[3] = {0, 0, 0};
for (size_t i = 0; i < data_string_len / 2; i++) {
buffer[0] = argv[0][i * 2];
buffer[1] = argv[0][i * 2 + 1];
uint8_t value = (uint8_t)strtol(buffer, NULL, 16);
shell_file->write(&value, 1);
}
// Wait two frame syncs to ensure action has painted
evtd->wait_finish_frame();
evtd->wait_finish_frame();
chprintf(chp, "ok\r\n");
}
@@ -590,7 +411,7 @@ static void cmd_rtcget(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argv;
rtc::RTC datetime;
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
chprintf(chp, "Current time: %04d-%02d-%02d %02d:%02d:%02d\r\n", datetime.year(), datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), datetime.second());
}
@@ -606,11 +427,12 @@ static void cmd_rtcset(BaseSequentialStream* chp, int argc, char* argv[]) {
return;
}
// TODO: additional commands/parameters for DST?
rtc::RTC new_datetime{
(uint16_t)strtol(argv[0], NULL, 10), (uint8_t)strtol(argv[1], NULL, 10),
(uint8_t)strtol(argv[2], NULL, 10), (uint32_t)strtol(argv[3], NULL, 10),
(uint32_t)strtol(argv[4], NULL, 10), (uint32_t)strtol(argv[5], NULL, 10)};
rtcSetTime(&RTCD1, &new_datetime);
rtc_time::set(new_datetime);
chprintf(chp, "ok\r\n");
}
@@ -843,6 +665,103 @@ static void cmd_accessibility_readcurr(BaseSequentialStream* chp, int argc, char
chprintf(chp, "\r\nok\r\n");
}
static void cmd_appstart(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argc;
(void)argv;
if (argc != 1) {
chprintf(chp, "Usage: appstart APPCALLNAME");
return;
}
auto evtd = getEventDispatcherInstance();
if (!evtd) return;
auto top_widget = evtd->getTopWidget();
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
if (nav->StartAppByName(argv[0])) {
chprintf(chp, "ok\r\n");
return;
}
// since ext app loader changed, we can just pass the string to it, and it"ll return if started or not.
std::string appwithpath = "/APPS/";
appwithpath += argv[0];
appwithpath += ".ppma";
bool ret = ui::ExternalItemsMenuLoader::run_external_app(*nav, path_from_string8((char*)appwithpath.c_str()));
if (!ret) {
chprintf(chp, "error\r\n");
return;
}
chprintf(chp, "ok\r\n");
}
static void printAppInfo(BaseSequentialStream* chp, ui::AppInfoConsole& element) {
if (strlen(element.appCallName) == 0) return;
chprintf(chp, element.appCallName);
chprintf(chp, " ");
chprintf(chp, element.appFriendlyName);
chprintf(chp, " ");
switch (element.appLocation) {
case RX:
chprintf(chp, "[RX]\r\n");
break;
case TX:
chprintf(chp, "[TX]\r\n");
break;
case UTILITIES:
chprintf(chp, "[UTIL]\r\n");
break;
case DEBUG:
chprintf(chp, "[DEBUG]\r\n");
break;
default:
break;
}
}
static void printAppInfo(BaseSequentialStream* chp, const ui::AppInfo& element) {
if (strlen(element.id) == 0) return;
chprintf(chp, element.id);
chprintf(chp, " ");
chprintf(chp, element.displayName);
chprintf(chp, " ");
switch (element.menuLocation) {
case RX:
chprintf(chp, "[RX]\r\n");
break;
case TX:
chprintf(chp, "[TX]\r\n");
break;
case UTILITIES:
chprintf(chp, "[UTIL]\r\n");
break;
case DEBUG:
chprintf(chp, "[DEBUG]\r\n");
break;
default:
break;
}
}
// returns the installed apps, those can be called by appstart APPNAME
static void cmd_applist(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argc;
(void)argv;
auto evtd = getEventDispatcherInstance();
if (!evtd) return;
auto top_widget = evtd->getTopWidget();
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
// TODO(u-foka): Somehow order static and dynamic app lists together
for (auto& element : ui::NavigationView::appMap) { // Use the map as its ordered by id
printAppInfo(chp, element.second);
}
ui::ExternalItemsMenuLoader::load_all_external_items_callback([chp](ui::AppInfoConsole& info) {
printAppInfo(chp, info);
});
chprintf(chp, "ok\r\n");
}
static void cmd_cpld_read(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage =
"usage: cpld_read <device> <target>\r\n"
@@ -1004,6 +923,132 @@ static void cmd_cpld_read(BaseSequentialStream* chp, int argc, char* argv[]) {
}
}
static void cmd_gotgps(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: gotgps <lat> <lon> [altitude] [speed]\r\n";
if (argc < 2 || argc > 4) {
chprintf(chp, usage);
return;
}
float lat = atof(argv[0]);
float lon = atof(argv[1]);
int32_t altitude = 0;
int32_t speed = 0;
if (argc >= 3) altitude = strtol(argv[2], NULL, 10);
if (argc >= 4) speed = strtol(argv[3], NULL, 10);
GPSPosDataMessage msg{lat, lon, altitude, speed};
EventDispatcher::send_message(msg);
chprintf(chp, "ok\r\n");
}
static void cmd_gotorientation(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: gotorientation <angle>\r\n";
if (argc != 1) {
chprintf(chp, usage);
return;
}
uint16_t angle = strtol(argv[0], NULL, 10);
OrientationDataMessage msg{angle};
EventDispatcher::send_message(msg);
chprintf(chp, "ok\r\n");
}
static void cmd_sysinfo(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: sysinfo\r\n";
(void)argv;
if (argc > 0) {
chprintf(chp, usage);
return;
}
auto utilisation = get_cpu_utilisation_in_percent();
std::string info =
"M0 heap: " + to_string_dec_uint(chCoreStatus()) + "\r\n" +
"M0 stack: " + to_string_dec_uint((uint32_t)get_free_stack_space()) + "\r\n" +
"M0 cpu%: " + to_string_dec_uint(utilisation) + "\r\n" +
"M4 heap: " + to_string_dec_uint(shared_memory.m4_heap_usage) + "\r\n" +
"M4 stack: " + to_string_dec_uint(shared_memory.m4_stack_usage) + "\r\n" +
"M0 cpu%: " + to_string_dec_uint(shared_memory.m4_performance_counter) + "\r\n" +
"M4 miss: " + to_string_dec_uint(shared_memory.m4_buffer_missed) + "\r\n" +
"uptime: " + to_string_dec_uint(chTimeNow() / 1000) + "\r\n";
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)info.c_str(), info.length());
return;
}
static void cmd_radioinfo(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: radioinfo\r\n";
(void)argv;
if (argc > 0) {
chprintf(chp, usage);
return;
}
std::string info =
"receiver_model.target_frequency: " + to_string_dec_uint(portapack::receiver_model.target_frequency()) + "\r\n" +
"receiver_model.baseband_bandwidth: " + to_string_dec_uint(portapack::receiver_model.baseband_bandwidth()) + "\r\n" +
"receiver_model.sampling_rate: " + to_string_dec_uint(portapack::receiver_model.sampling_rate()) + "\r\n" +
"receiver_model.modulation: " + to_string_dec_uint((uint32_t)portapack::receiver_model.modulation()) + "\r\n" +
"receiver_model.am_configuration: " + to_string_dec_uint(portapack::receiver_model.am_configuration()) + "\r\n" +
"receiver_model.nbfm_configuration: " + to_string_dec_uint(portapack::receiver_model.nbfm_configuration()) + "\r\n" +
"receiver_model.wfm_configuration: " + to_string_dec_uint(portapack::receiver_model.wfm_configuration()) + "\r\n" +
"transmitter_model.target_frequency: " + to_string_dec_uint(portapack::transmitter_model.target_frequency()) + "\r\n" +
"transmitter_model.baseband_bandwidth: " + to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth()) + "\r\n" +
"transmitter_model.sampling_rate: " + to_string_dec_uint(portapack::transmitter_model.sampling_rate()) + "\r\n";
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)info.c_str(), info.length());
return;
}
static void cmd_pmemreset(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: pmemreset yes\r\nThis will reset pmem to defaults!\r\n";
(void)argv;
if (argc != 1 || strcmp(argv[0], "yes") != 0) {
chprintf(chp, usage);
return;
}
auto evtd = getEventDispatcherInstance();
if (!evtd) return;
auto top_widget = evtd->getTopWidget();
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
nav->home(true);
portapack::persistent_memory::cache::defaults();
// system refresh
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
chprintf(chp, "ok\r\n");
}
static void cmd_settingsreset(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: settingsreset yes\r\nThis will reset all app settings to defaults!\r\n";
(void)argv;
if (argc != 1 || strcmp(argv[0], "yes") != 0) {
chprintf(chp, usage);
return;
}
auto evtd = getEventDispatcherInstance();
if (!evtd) return;
auto top_widget = evtd->getTopWidget();
if (!top_widget) return;
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
if (!nav) return;
nav->home(true); // to exit all running apps
for (const auto& entry : std::filesystem::directory_iterator(SETTINGS_DIR, u"*.ini")) {
if (std::filesystem::is_regular_file(entry.status())) {
std::filesystem::path pth = SETTINGS_DIR;
pth += u"/" + entry.path();
chprintf(chp, pth.string().c_str());
chprintf(chp, "\r\n");
f_unlink(pth.tchar());
}
}
// system refresh
StatusRefreshMessage message{};
EventDispatcher::send_message(message);
chprintf(chp, "ok\r\n");
}
static const ShellCommand commands[] = {
{"reboot", cmd_reboot},
{"dfu", cmd_dfu},
@@ -1018,20 +1063,21 @@ static const ShellCommand commands[] = {
{"button", cmd_button},
{"touch", cmd_touch},
{"keyboard", cmd_keyboard},
{"ls", cmd_sd_list_dir},
{"rm", cmd_sd_delete},
{"open", cmd_sd_open},
{"seek", cmd_sd_seek},
{"close", cmd_sd_close},
{"read", cmd_sd_read},
{"write", cmd_sd_write},
{"filesize", cmd_sd_filesize},
USB_SERIAL_SHELL_SD_COMMANDS,
{"rtcget", cmd_rtcget},
{"rtcset", cmd_rtcset},
{"cpld_info", cpld_info},
{"cpld_read", cmd_cpld_read},
{"accessibility_readall", cmd_accessibility_readall},
{"accessibility_readcurr", cmd_accessibility_readcurr},
{"applist", cmd_applist},
{"appstart", cmd_appstart},
{"gotgps", cmd_gotgps},
{"gotorientation", cmd_gotorientation},
{"sysinfo", cmd_sysinfo},
{"radioinfo", cmd_radioinfo},
{"pmemreset", cmd_pmemreset},
{"settingsreset", cmd_settingsreset},
{NULL, NULL}};
static const ShellConfig shell_cfg1 = {
@@ -1040,5 +1086,5 @@ static const ShellConfig shell_cfg1 = {
void create_shell(EventDispatcher* evtd) {
_eventDispatcherInstance = evtd;
shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO);
shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO + 10);
}
@@ -0,0 +1,373 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "usb_serial_shell_filesystem.hpp"
#include "usb_serial_device_to_host.h"
#include "chprintf.h"
#include "string_format.hpp"
#include <cstring>
static File* shell_file = nullptr;
static bool report_on_error(BaseSequentialStream* chp, File::Error& error) {
if (error.ok() == false) {
chprintf(chp, "Error calling delete_file: %d %s\r\n", error.code(), error.what().c_str());
return true;
}
return false;
}
static bool report_on_error(BaseSequentialStream* chp, FRESULT error_code) {
std::filesystem::filesystem_error error = error_code;
return report_on_error(chp, error);
}
static bool report_on_error(BaseSequentialStream* chp, Optional<File::Error>& error) {
if (error.is_valid())
return report_on_error(chp, error.value());
return false;
}
static bool report_on_error(BaseSequentialStream* chp, File::Result<uint64_t> error) {
if (error.is_error()) {
return report_on_error(chp, error.error());
}
return false;
}
void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: ls /\r\n");
return;
}
auto path = path_from_string8(argv[0]);
for (const auto& entry : std::filesystem::directory_iterator(path, "*")) {
if (std::filesystem::is_directory(entry.status())) {
chprintf(chp, "%s/\r\n", entry.path().string().c_str());
} else if (std::filesystem::is_regular_file(entry.status())) {
chprintf(chp, "%s\r\n", entry.path().string().c_str());
} else {
chprintf(chp, "%s *\r\n", entry.path().string().c_str());
}
}
}
void cmd_sd_unlink(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: unlink <path>\r\n");
return;
}
auto path = path_from_string8(argv[0]);
auto error = delete_file(path);
if (report_on_error(chp, error)) return;
chprintf(chp, "ok\r\n");
}
void cmd_sd_mkdir(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: mkdir <path>\r\n");
return;
}
auto path = path_from_string8(argv[0]);
if (!std::filesystem::is_directory(path)) {
chprintf(chp, "directory already exists.\r\n");
return;
}
auto error = make_new_directory(path);
if (report_on_error(chp, error)) return;
chprintf(chp, "ok\r\n");
}
void cmd_sd_filesize(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: filesize <path>\r\n");
return;
}
auto path = path_from_string8(argv[0]);
FILINFO res;
auto stat = f_stat(path.tchar(), &res);
if (report_on_error(chp, stat)) return;
chprintf(chp, "%lu\r\n", res.fsize);
chprintf(chp, "ok\r\n");
}
void cmd_sd_open(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: fopen <path>\r\n");
return;
}
if (shell_file != nullptr) {
chprintf(chp, "file already open\r\n");
return;
}
auto path = path_from_string8(argv[0]);
shell_file = new File();
auto error = shell_file->open(path, false, true);
if (report_on_error(chp, error)) return;
chprintf(chp, "ok\r\n");
}
void cmd_sd_seek(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: fseek <offset>\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
int address = (int)strtol(argv[0], NULL, 10);
auto error = shell_file->seek(address);
if (report_on_error(chp, error)) return;
chprintf(chp, "ok\r\n");
}
void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argv;
if (argc != 0) {
chprintf(chp, "usage: fclose\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
auto error = shell_file->sync();
if (report_on_error(chp, error)) return;
delete shell_file;
shell_file = nullptr;
chprintf(chp, "ok\r\n");
}
void cmd_sd_truncate(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argv;
if (argc != 0) {
chprintf(chp, "usage: ftruncate\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
auto error = shell_file->truncate();
if (report_on_error(chp, error)) return;
chprintf(chp, "ok\r\n");
}
void cmd_sd_sync(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argv;
if (argc != 0) {
chprintf(chp, "usage: fsync\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
auto error = shell_file->sync();
if (report_on_error(chp, error)) return;
chprintf(chp, "ok\r\n");
}
void cmd_sd_tell(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argv;
if (argc != 0) {
chprintf(chp, "usage: ftell\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
auto current_position = shell_file->tell();
chprintf(chp, "%d\r\n", current_position);
chprintf(chp, "ok\r\n");
}
void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: fread <number of bytes>\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
int size = (int)strtol(argv[0], NULL, 10);
uint8_t buffer[62];
do {
File::Size bytes_to_read = size > 62 ? 62 : size;
auto bytes_read = shell_file->read(buffer, bytes_to_read);
if (report_on_error(chp, bytes_read)) return;
std::string res = to_string_hex_array(buffer, bytes_read.value());
res += "\r\n";
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)res.c_str(), res.size());
if (bytes_to_read != bytes_read.value())
return;
size -= bytes_to_read;
} while (size > 0);
chprintf(chp, "ok\r\n");
}
void cmd_sd_read_binary(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: frb <number of bytes>\r\n");
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
int size = (int)strtol(argv[0], NULL, 10);
uint8_t buffer[64];
do {
File::Size bytes_to_read = size > 64 ? 64 : size;
auto bytes_read = shell_file->read(buffer, bytes_to_read);
if (report_on_error(chp, bytes_read)) return;
if (bytes_read.value() > 0)
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, buffer, bytes_read.value());
if (bytes_to_read != bytes_read.value())
return;
size -= bytes_to_read;
} while (size > 0);
chprintf(chp, "\r\nok\r\n");
}
void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: fwrite 0123456789ABCDEF\r\n";
if (argc != 1) {
chprintf(chp, usage);
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
size_t data_string_len = strlen(argv[0]);
if (data_string_len % 2 != 0) {
chprintf(chp, usage);
return;
}
for (size_t i = 0; i < data_string_len; i++) {
char c = argv[0][i];
if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
chprintf(chp, usage);
return;
}
}
char buffer[3] = {0, 0, 0};
for (size_t i = 0; i < data_string_len / 2; i++) {
buffer[0] = argv[0][i * 2];
buffer[1] = argv[0][i * 2 + 1];
uint8_t value = (uint8_t)strtol(buffer, NULL, 16);
auto error = shell_file->write(&value, 1);
if (report_on_error(chp, error)) return;
}
chprintf(chp, "ok\r\n");
}
void cmd_sd_write_binary(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage = "usage: fwb <number of bytes>\r\nfollowed by <number of bytes> of data";
if (argc != 1) {
chprintf(chp, usage);
return;
}
if (shell_file == nullptr) {
chprintf(chp, "no open file\r\n");
return;
}
size_t size = (size_t)strtol(argv[0], NULL, 10);
chprintf(chp, "send %d bytes\r\n", size);
uint8_t buffer[USB_BULK_BUFFER_SIZE];
do {
size_t bytes_to_read = size > USB_BULK_BUFFER_SIZE ? USB_BULK_BUFFER_SIZE : size;
size_t bytes_read = chSequentialStreamRead(chp, &buffer[0], bytes_to_read);
if (bytes_read != bytes_to_read)
return;
auto error = shell_file->write(&buffer[0], bytes_read);
if (report_on_error(chp, error)) return;
size -= bytes_read;
} while (size > 0);
chprintf(chp, "ok\r\n");
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ch.h"
#include "hal.h"
#include <locale>
#include <codecvt>
#include "ff.h"
#include "file.hpp"
void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_unlink(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_mkdir(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_filesize(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_open(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_seek(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_truncate(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_sync(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_tell(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_read_binary(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]);
void cmd_sd_write_binary(BaseSequentialStream* chp, int argc, char* argv[]);
static std::filesystem::path path_from_string8(char* path) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
return conv.from_bytes(path);
}
// clang-format off
#define USB_SERIAL_SHELL_SD_COMMANDS \
{"ls", cmd_sd_list_dir}, \
{"unlink", cmd_sd_unlink}, \
{"mkdir", cmd_sd_mkdir}, \
{"filesize", cmd_sd_filesize}, \
{"fopen", cmd_sd_open}, \
{"fseek", cmd_sd_seek}, \
{"fclose", cmd_sd_close}, \
{"ftruncate", cmd_sd_truncate}, \
{"fsync", cmd_sd_sync}, \
{"ftell", cmd_sd_tell}, \
{"fread", cmd_sd_read}, \
{"frb", cmd_sd_read_binary}, \
{"fwrite", cmd_sd_write}, \
{"fwb", cmd_sd_write_binary}
// clang-format on
+1 -1
View File
@@ -30,7 +30,7 @@
#include "optional.hpp"
#include "chprintf.h"
#include "usb_serial_io.h"
#include "usb_serial_device_to_host.h"
#include <cstdint>
#include <cstddef>
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __VIEW_FACTORY_HPP__
#define __VIEW_FACTORY_HPP__
#include "view_factory_base.hpp"
namespace ui {
template <typename T>
class ViewFactory : public ViewFactoryBase {
public:
virtual std::unique_ptr<View> produce(NavigationView& nav) const override {
return std::unique_ptr<View>(new T(nav));
}
};
} // namespace ui
#endif //__VIEW_FACTORY_HPP__
@@ -0,0 +1,28 @@
/*
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "view_factory_base.hpp"
namespace ui {
ViewFactoryBase::~ViewFactoryBase() {}
} // namespace ui
@@ -0,0 +1,40 @@
/*
* Copyright 2024 Tamas Eisenberger <e.tamas@iwstudio.hu>
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __VIEW_FACTORY_BASE_HPP__
#define __VIEW_FACTORY_BASE_HPP__
#include <memory>
#include "ui_widget.hpp"
namespace ui {
class NavigationView;
class ViewFactoryBase {
public:
virtual ~ViewFactoryBase();
virtual std::unique_ptr<View> produce(NavigationView& nav) const = 0;
};
} // namespace ui
#endif //__VIEW_FACTORY_BASE_HPP__
+14 -4
View File
@@ -123,10 +123,10 @@ constexpr gpdma::channel::Config config_rx() {
/* TODO: Clean up terminology around "buffer", "transfer", "samples" */
constexpr size_t buffer_samples_log2n = 7;
constexpr size_t buffer_samples = (1 << buffer_samples_log2n);
constexpr size_t buffer_samples = (1 << buffer_samples_log2n); // 2^7 = 128 byte circular DMA buffer
constexpr size_t transfers_per_buffer_log2n = 2;
constexpr size_t transfers_per_buffer = (1 << transfers_per_buffer_log2n);
constexpr size_t transfer_samples = buffer_samples / transfers_per_buffer;
constexpr size_t transfers_per_buffer = (1 << transfers_per_buffer_log2n); // 2^2 = 4 transfer buffers in the circular buffer
constexpr size_t transfer_samples = buffer_samples / transfers_per_buffer; // 128/4 = 32 samples in each transfer buffer
constexpr size_t transfers_mask = transfers_per_buffer - 1;
constexpr size_t buffer_bytes = buffer_samples * sizeof(sample_t);
@@ -144,6 +144,8 @@ static constexpr auto& gpdma_channel_i2s0_rx = gpdma::channels[portapack::i2s0_r
static volatile const gpdma::channel::LLI* tx_next_lli = nullptr;
static volatile const gpdma::channel::LLI* rx_next_lli = nullptr;
static bool single_tx_buffer = false;
static void tx_transfer_complete() {
tx_next_lli = gpdma_channel_i2s0_tx.next_lli();
}
@@ -213,11 +215,19 @@ void disable() {
gpdma_channel_i2s0_rx.disable();
}
void shrink_tx_buffer(bool shrink) {
single_tx_buffer = shrink;
if (single_tx_buffer)
lli_tx_loop[0].lli = lli_pointer(&lli_tx_loop[0]);
else
lli_tx_loop[0].lli = lli_pointer(&lli_tx_loop[1]);
}
buffer_t tx_empty_buffer() {
const auto next_lli = tx_next_lli;
if (next_lli) {
const size_t next_index = next_lli - &lli_tx_loop[0];
const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask;
const size_t free_index = (single_tx_buffer) ? 0 : (next_index + transfers_per_buffer - 2) & transfers_mask;
return {reinterpret_cast<sample_t*>(lli_tx_loop[free_index].srcaddr), transfer_samples};
} else {
return {nullptr, 0};
+1
View File
@@ -47,6 +47,7 @@ void init();
void configure();
void enable();
void disable();
void shrink_tx_buffer(bool shrink);
audio::buffer_t tx_empty_buffer();
audio::buffer_t rx_empty_buffer();
+36 -16
View File
@@ -32,34 +32,34 @@
#include <cstddef>
#include <array>
void AudioOutput::configure(
const bool do_proc) {
void AudioOutput::configure(const bool do_proc) {
do_processing = do_proc;
}
void AudioOutput::configure(
const iir_biquad_config_t& hpf_config,
const iir_biquad_config_t& deemph_config,
const float squelch_threshold) {
void AudioOutput::configure(const iir_biquad_config_t& hpf_config, const iir_biquad_config_t& deemph_config, const float squelch_threshold) {
hpf.configure(hpf_config);
deemph.configure(deemph_config);
squelch.set_threshold(squelch_threshold);
}
void AudioOutput::write(
const buffer_s16_t& audio) {
void AudioOutput::write_unprocessed(const buffer_s16_t& audio) {
block_buffer_s16.feed(
audio,
[this](const buffer_s16_t& buffer) {
audio_present = true;
fill_audio_buffer(buffer, audio_present);
});
}
void AudioOutput::write(const buffer_s16_t& audio) {
std::array<float, 32> audio_f;
for (size_t i = 0; i < audio.count; i++) {
audio_f[i] = audio.p[i] * ki;
}
write(buffer_f32_t{
audio_f.data(),
audio.count,
audio.sampling_rate});
write(buffer_f32_t{audio_f.data(), audio.count, audio.sampling_rate});
}
void AudioOutput::write(
const buffer_f32_t& audio) {
void AudioOutput::write(const buffer_f32_t& audio) {
block_buffer.feed(
audio,
[this](const buffer_f32_t& buffer) {
@@ -67,8 +67,7 @@ void AudioOutput::write(
});
}
void AudioOutput::on_block(
const buffer_f32_t& audio) {
void AudioOutput::on_block(const buffer_f32_t& audio) {
if (do_processing) {
const auto audio_present_now = squelch.execute(audio);
@@ -93,6 +92,18 @@ bool AudioOutput::is_squelched() {
return !audio_present;
}
void AudioOutput::fill_audio_buffer(const buffer_s16_t& audio, const bool send_to_fifo) {
auto audio_buffer = audio::dma::tx_empty_buffer();
for (size_t i = 0; i < audio_buffer.count; i++) {
audio_buffer.p[i].left = audio_buffer.p[i].right = audio.p[i];
}
if (stream && send_to_fifo) {
stream->write(audio.p, audio_buffer.count * sizeof(int16_t));
}
feed_audio_stats(audio);
}
void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo) {
std::array<int16_t, 32> audio_int;
@@ -110,6 +121,15 @@ void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_t
feed_audio_stats(audio);
}
void AudioOutput::feed_audio_stats(const buffer_s16_t& audio) {
audio_stats.feed(
audio,
[](const AudioStatistics& statistics) {
const AudioStatisticsMessage audio_stats_message{statistics};
shared_memory.application_queue.push(audio_stats_message);
});
}
void AudioOutput::feed_audio_stats(const buffer_f32_t& audio) {
audio_stats.feed(
audio,
+6
View File
@@ -44,6 +44,7 @@ class AudioOutput {
const iir_biquad_config_t& deemph_config = iir_config_passthrough,
const float squelch_threshold = 0.0f);
void write_unprocessed(const buffer_s16_t& audio);
void write(const buffer_s16_t& audio);
void write(const buffer_f32_t& audio);
@@ -57,6 +58,7 @@ class AudioOutput {
static constexpr float k = 32768.0f;
static constexpr float ki = 1.0f / k;
BlockDecimator<int16_t, 32> block_buffer_s16{1};
BlockDecimator<float, 32> block_buffer{1};
IIRBiquadFilter hpf{};
@@ -73,7 +75,11 @@ class AudioOutput {
bool do_processing = true;
void on_block(const buffer_f32_t& audio);
void fill_audio_buffer(const buffer_s16_t& audio, const bool send_to_fifo);
void fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo);
void feed_audio_stats(const buffer_s16_t& audio);
void feed_audio_stats(const buffer_f32_t& audio);
};
@@ -23,6 +23,19 @@
#include "utility.hpp"
void AudioStatsCollector::consume_audio_buffer(const buffer_s16_t& src) {
auto src_p = src.p;
const auto src_end = &src.p[src.count];
while (src_p < src_end) {
const auto sample = *(src_p++);
const auto sample_squared = sample * sample;
squared_sum += sample_squared;
if (sample_squared > max_squared) {
max_squared = sample_squared;
}
}
}
void AudioStatsCollector::consume_audio_buffer(const buffer_f32_t& src) {
auto src_p = src.p;
const auto src_end = &src.p[src.count];
@@ -56,6 +69,12 @@ bool AudioStatsCollector::update_stats(const size_t sample_count, const size_t s
}
}
bool AudioStatsCollector::feed(const buffer_s16_t& src) {
consume_audio_buffer(src);
return update_stats(src.count, src.sampling_rate);
}
bool AudioStatsCollector::feed(const buffer_f32_t& src) {
consume_audio_buffer(src);
@@ -30,6 +30,13 @@
class AudioStatsCollector {
public:
template <typename Callback>
void feed(const buffer_s16_t& src, Callback callback) {
if (feed(src)) {
callback(statistics);
}
}
template <typename Callback>
void feed(const buffer_f32_t& src, Callback callback) {
if (feed(src)) {
@@ -52,10 +59,12 @@ class AudioStatsCollector {
AudioStatistics statistics{};
void consume_audio_buffer(const buffer_s16_t& src);
void consume_audio_buffer(const buffer_f32_t& src);
bool update_stats(const size_t sample_count, const size_t sampling_rate);
bool feed(const buffer_s16_t& src);
bool feed(const buffer_f32_t& src);
bool mute(const size_t sample_count, const size_t sampling_rate);
};
+14
View File
@@ -95,6 +95,20 @@ void BasebandThread::run() {
buffer_c8_t buffer{
buffer_tmp.p, buffer_tmp.count, sampling_rate_};
if (shared_memory.request_m4_performance_counter == 0x02) {
uint8_t max = shared_memory.m4_performance_counter;
for (size_t i = 0; i < buffer_tmp.count; i++) {
int8_t a = buffer_tmp.p[i].real();
if (a < 0)
a = -a;
if (a > max)
max = a;
}
shared_memory.m4_performance_counter = max;
}
if (baseband_processor_) {
baseband_processor_->execute(buffer);
}
+10 -6
View File
@@ -135,13 +135,17 @@ void update_performance_counters() {
last_paint_state = !last_paint_state;
auto utilisation = get_cpu_utilisation_in_percent();
auto free_stack = (uint32_t)get_free_stack_space();
auto free_heap = chCoreStatus();
if (performance_counter_active == 0x01) {
auto utilisation = get_cpu_utilisation_in_percent();
auto free_stack = (uint32_t)get_free_stack_space();
auto free_heap = chCoreStatus();
shared_memory.m4_cpu_usage = utilisation;
shared_memory.m4_stack_usage = free_stack;
shared_memory.m4_heap_usage = free_heap;
shared_memory.m4_performance_counter = utilisation;
shared_memory.m4_stack_usage = free_stack;
shared_memory.m4_heap_usage = free_heap;
} else if (performance_counter_active == 0x02) {
shared_memory.m4_performance_counter = 0;
}
}
} /* extern "C" */
+15 -1
View File
@@ -41,7 +41,17 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
}
}
sample = tone_gen.process(audio_sample - 0x80);
sample = audio_sample - 0x80;
// Output to speaker too
if (!tone_key_enabled) {
uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1);
audio_data[imod32] = sample * 256;
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
audio_output.write_unprocessed(audio_buffer);
}
sample = tone_gen.process(sample);
// FM
delta = sample * fm_delta;
@@ -95,6 +105,10 @@ void AudioTXProcessor::audio_config(const AudioTXConfigMessage& message) {
tone_gen.configure(message.tone_key_delta, message.tone_key_mix_weight);
progress_interval_samples = message.divider;
resample_acc = 0;
audio_output.configure(false);
tone_key_enabled = (message.tone_key_delta != 0);
audio::dma::shrink_tx_buffer(!tone_key_enabled);
}
void AudioTXProcessor::replay_config(const ReplayConfigMessage& message) {
+9
View File
@@ -27,6 +27,10 @@
#include "baseband_thread.hpp"
#include "tone_gen.hpp"
#include "stream_output.hpp"
#include "audio_output.hpp"
#include "audio_dma.hpp"
#define AUDIO_OUTPUT_BUFFER_SIZE 32
class AudioTXProcessor : public BasebandProcessor {
public:
@@ -48,10 +52,15 @@ class AudioTXProcessor : public BasebandProcessor {
int32_t sample{0}, delta{};
int8_t re{0}, im{0};
int16_t audio_data[AUDIO_OUTPUT_BUFFER_SIZE];
buffer_s16_t audio_buffer{audio_data, AUDIO_OUTPUT_BUFFER_SIZE, 48000};
AudioOutput audio_output{};
size_t progress_interval_samples = 0, progress_samples = 0;
bool configured{false};
uint32_t bytes_read{0};
bool tone_key_enabled{false};
void sample_rate_config(const SampleRateConfigMessage& message);
void audio_config(const AudioTXConfigMessage& message);
@@ -186,7 +186,7 @@ uint8_t usb_descriptor_string_manufacturer[] = {
uint8_t usb_descriptor_string_product[] = {
#ifdef HACKRF_ONE
43, // bLength
34, // bLength
USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType
'P', 0x00,
'o', 0x00,
+4 -2
View File
@@ -26,7 +26,8 @@
#include "ui_navigation.hpp"
#include "spi_image.hpp"
#define CURRENT_HEADER_VERSION 0x00000001
#define CURRENT_HEADER_VERSION 0x00000002
#define MIN_HEADER_VERSION_FOR_CHECKSUM 0x00000002
typedef void (*externalAppEntry_t)(ui::NavigationView& nav);
@@ -34,7 +35,8 @@ enum app_location_t : uint32_t {
UTILITIES = 0,
RX,
TX,
DEBUG
DEBUG,
HOME
};
struct application_information_t {
+31
View File
@@ -118,6 +118,8 @@ class Message {
SubGhzFPRxConfigure = 60,
WeatherData = 61,
SubGhzDData = 62,
GPSPosData = 63,
OrientationData = 64,
MAX
};
@@ -1301,4 +1303,33 @@ class SubGhzDDataMessage : public Message {
uint64_t data = 0;
};
class GPSPosDataMessage : public Message {
public:
constexpr GPSPosDataMessage(
float lat = 200.0,
float lon = 200.0,
int32_t altitude = 0,
int32_t speed = 0)
: Message{ID::GPSPosData},
lat{lat},
lon{lon},
altitude{altitude},
speed{speed} {
}
float lat = 200.0;
float lon = 200.0;
int32_t altitude = 0;
int32_t speed = 0;
};
class OrientationDataMessage : public Message {
public:
constexpr OrientationDataMessage(
uint16_t angle = 400)
: Message{ID::OrientationData},
angle{angle} {
}
uint16_t angle = 400; //>360 -> no orientation set
};
#endif /*__MESSAGE_H__*/
+60 -14
View File
@@ -32,7 +32,9 @@
#include "string_format.hpp"
#include "ui_styles.hpp"
#include "ui_painter.hpp"
#include "ui_flash_utility.hpp"
#include "utility.hpp"
#include "rtc_time.hpp"
#include <algorithm>
#include <string>
@@ -145,7 +147,7 @@ struct misc_config_t {
bool disable_speaker : 1;
bool config_disable_external_tcxo : 1;
bool config_sdcard_high_speed_io : 1;
bool UNUSED_4 : 1;
bool config_disable_config_mode : 1;
bool UNUSED_5 : 1;
bool UNUSED_6 : 1;
bool UNUSED_7 : 1;
@@ -156,6 +158,8 @@ struct misc_config_t {
};
static_assert(sizeof(misc_config_t) == sizeof(uint32_t));
#define MC_CONFIG_DISABLE_CONFIG_MODE 0x00000010 // config_disable_config_mode bit in struct above
/* IMPORTANT: Update dump_persistent_memory (below) when changing data_t. */
/* Struct must pack the same way on M4 and M0 cores.
@@ -233,6 +237,9 @@ struct data_t {
// recovery mode magic value storage
uint32_t config_mode_storage;
// Daylight savings time
dst_config_t dst_config;
constexpr data_t()
: structure_version(data_structure_version_enum::VERSION_CURRENT),
target_frequency(target_frequency_reset_value),
@@ -285,7 +292,8 @@ struct data_t {
headphone_volume_cb(-600),
misc_config(),
ui_config2(),
config_mode_storage(CONFIG_MODE_NORMAL_VALUE) {
config_mode_storage(CONFIG_MODE_NORMAL_VALUE),
dst_config() {
}
};
@@ -386,6 +394,7 @@ namespace cache {
void defaults() {
cached_backup_ram = backup_ram_t();
// If the desired default is 0/false, then no need to set it here (buffer is initialized to 0)
set_config_backlight_timer(backlight_config_t{});
set_config_splash(true);
set_config_disable_external_tcxo(false);
@@ -416,8 +425,9 @@ void init() {
const auto switches_state = get_switches_state();
// ignore for valid check
auto config_mode_backup = config_mode_storage();
set_config_mode_storage(CONFIG_MODE_NORMAL_VALUE);
auto config_mode_backup = config_mode_storage_direct();
set_config_mode_storage_direct(CONFIG_MODE_NORMAL_VALUE);
if (!(switches_state[(size_t)ui::KeyEvent::Left] && switches_state[(size_t)ui::KeyEvent::Right]) && backup_ram->is_valid()) {
// Copy valid persistent data into cache.
cached_backup_ram = *backup_ram;
@@ -433,7 +443,7 @@ void init() {
// Copy defaults into cache.
defaults();
}
set_config_mode_storage(config_mode_backup);
set_config_mode_storage_direct(config_mode_backup);
}
void persist() {
@@ -597,6 +607,10 @@ bool config_disable_external_tcxo() {
return data->misc_config.config_disable_external_tcxo;
}
bool config_disable_config_mode() {
return data->misc_config.config_disable_config_mode;
}
bool config_sdcard_high_speed_io() {
return data->misc_config.config_sdcard_high_speed_io;
}
@@ -666,6 +680,10 @@ void set_config_disable_external_tcxo(bool v) {
data->misc_config.config_disable_external_tcxo = v;
}
void set_config_disable_config_mode(bool v) {
data->misc_config.config_disable_config_mode = v;
}
void set_config_sdcard_high_speed_io(bool v, bool save) {
if (v) {
/* 200MHz / (2 * 2) = 50MHz */
@@ -947,12 +965,34 @@ void set_encoder_dial_sensitivity(uint8_t v) {
// Recovery mode magic value storage
static data_t* data_direct_access = reinterpret_cast<data_t*>(memory::map::backup_ram.base());
uint32_t config_mode_storage() {
uint32_t config_mode_storage_direct() {
return data_direct_access->config_mode_storage;
}
void set_config_mode_storage(uint32_t v) {
void set_config_mode_storage_direct(uint32_t v) {
data_direct_access->config_mode_storage = v;
}
bool config_disable_config_mode_direct() {
// "return data_direct_access->misc_config.config_disable_config_mode"
// Casting as U32 as workaround for misaligned memory access
uint32_t misc_config_u32 = *(uint32_t*)&data_direct_access->misc_config;
return ((misc_config_u32 & MC_CONFIG_DISABLE_CONFIG_MODE) != 0);
}
// Daylight savings time
bool dst_enabled() {
return data->dst_config.b.dst_enabled;
}
void set_dst_enabled(bool v) {
data->dst_config.b.dst_enabled = v;
rtc_time::dst_init();
}
dst_config_t config_dst() {
return data->dst_config;
}
void set_config_dst(dst_config_t v) {
data->dst_config = v;
rtc_time::dst_init();
}
// PMem to sdcard settings
@@ -963,7 +1003,7 @@ bool should_use_sdcard_for_pmem() {
int save_persistent_settings_to_file() {
File outfile;
make_new_directory(SETTINGS_DIR);
ensure_directory(SETTINGS_DIR);
auto error = outfile.create(PMEM_SETTING_FILE);
if (error)
return false;
@@ -996,7 +1036,7 @@ bool debug_dump() {
std::filesystem::path filename{};
File pmem_dump_file{};
// create new dump file name and DEBUG directory
make_new_directory(debug_dir);
ensure_directory(debug_dir);
filename = next_filename_matching_pattern(debug_dir + "/DEBUG_DUMP_????.TXT");
if (filename.empty()) {
painter.draw_string({0, 320 - 16}, ui::Styles::red, "COULD NOT GET DUMP NAME !");
@@ -1012,6 +1052,9 @@ bool debug_dump() {
pmem_dump_file.write_line("Ext APPS version req'd: 0x" + to_string_hex(VERSION_MD5));
pmem_dump_file.write_line("GCC version: " + to_string_dec_int(__GNUC__) + "." + to_string_dec_int(__GNUC_MINOR__) + "." + to_string_dec_int(__GNUC_PATCHLEVEL__));
// firmware checksum
pmem_dump_file.write_line("Firmware calculated checksum: 0x" + to_string_hex(simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE), 8));
// write persistent memory
pmem_dump_file.write_line("\n[Persistent Memory]");
@@ -1037,9 +1080,9 @@ bool debug_dump() {
pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(data->tone_mix));
pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(data->hardware_config));
pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(data->recon_config, 16));
pmem_dump_file.write_line("recon_repeat_nb: " + to_string_dec_int(data->recon_repeat_nb, 16));
pmem_dump_file.write_line("recon_repeat_gain:" + to_string_hex(data->recon_config, 16));
pmem_dump_file.write_line("recon_repeat_delay:" + to_string_hex(data->recon_config, 16));
pmem_dump_file.write_line("recon_repeat_nb: " + to_string_dec_int(data->recon_repeat_nb));
pmem_dump_file.write_line("recon_repeat_gain: " + to_string_dec_int(data->recon_repeat_gain));
pmem_dump_file.write_line("recon_repeat_delay: " + to_string_dec_int(data->recon_repeat_delay));
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->converter));
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->updown_converter));
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(data->updown_frequency_rx_correction));
@@ -1054,6 +1097,8 @@ bool debug_dump() {
pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(data->encoder_dial_sensitivity));
// pmem_dump_file.write_line("UNUSED_8: " + to_string_dec_uint(data->UNUSED_8));
pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb));
pmem_dump_file.write_line("config_mode_storage: 0x" + to_string_hex(data->config_mode_storage, 8));
pmem_dump_file.write_line("dst_config: 0x" + to_string_hex((uint32_t)data->dst_config.v, 8));
// ui_config bits
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
@@ -1086,8 +1131,9 @@ bool debug_dump() {
// misc_config bits
pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));
pmem_dump_file.write_line("misc_config config_speaker_disable: " + to_string_dec_int(config_speaker_disable()));
pmem_dump_file.write_line("ui_config config_disable_external_tcxo: " + to_string_dec_uint(config_disable_external_tcxo()));
pmem_dump_file.write_line("ui_config config_sdcard_high_speed_io: " + to_string_dec_uint(config_sdcard_high_speed_io()));
pmem_dump_file.write_line("misc_config config_disable_external_tcxo: " + to_string_dec_uint(config_disable_external_tcxo()));
pmem_dump_file.write_line("misc_config config_sdcard_high_speed_io: " + to_string_dec_uint(config_sdcard_high_speed_io()));
pmem_dump_file.write_line("misc_config config_disable_config_mode: " + to_string_dec_uint(config_disable_config_mode()));
// receiver_model
pmem_dump_file.write_line("\n[Receiver Model]");
@@ -32,11 +32,12 @@
#include "modems.hpp"
#include "serializer.hpp"
#include "volume.hpp"
#include "config_mode.hpp"
// persistant memory from/to sdcard flag file
// persistent memory from/to sdcard flag file
#define PMEM_FILEFLAG u"/SETTINGS/PMEM_FILEFLAG"
// persistant memory from/to sdcard flag file
// persistent memory from/to sdcard flag file
#define PMEM_SETTING_FILE u"/SETTINGS/pmem_settings"
#define PMEM_SIZE_BYTES 256 // total amount of pmem space in bytes, including checksum
@@ -116,6 +117,21 @@ enum encoder_dial_sensitivity {
NUM_DIAL_SENSITIVITY
};
typedef union {
uint32_t v;
struct {
uint8_t start_which : 4;
uint8_t start_weekday : 4;
uint8_t start_month : 4;
uint8_t end_which : 4;
uint8_t end_weekday : 4;
uint8_t end_month : 4;
uint8_t UNUSED : 7;
uint8_t dst_enabled : 1;
} b;
} dst_config_t;
static_assert(sizeof(dst_config_t) == sizeof(uint32_t));
namespace cache {
/* Set values in cache to sensible defaults. */
@@ -178,6 +194,8 @@ void set_config_cpld(uint8_t i);
bool config_disable_external_tcxo();
bool config_sdcard_high_speed_io();
bool config_disable_config_mode();
bool config_splash();
bool config_converter();
bool config_updown_converter();
@@ -198,6 +216,8 @@ void set_save_app_settings(bool v);
void set_show_bigger_qr_code(bool v);
void set_config_disable_external_tcxo(bool v);
void set_config_sdcard_high_speed_io(bool v, bool save);
void set_config_disable_config_mode(bool v);
void set_config_splash(bool v);
bool config_converter();
bool config_updown_converter();
@@ -223,10 +243,9 @@ void set_disable_touchscreen(bool v);
uint8_t config_encoder_dial_sensitivity();
void set_encoder_dial_sensitivity(uint8_t v);
#define CONFIG_MODE_GUARD_VALUE 2001
#define CONFIG_MODE_NORMAL_VALUE 1999
uint32_t config_mode_storage();
void set_config_mode_storage(uint32_t v);
uint32_t config_mode_storage_direct();
void set_config_mode_storage_direct(uint32_t v);
bool config_disable_config_mode_direct();
uint32_t pocsag_last_address();
void set_pocsag_last_address(uint32_t address);
@@ -236,9 +255,14 @@ void set_pocsag_ignore_address(uint32_t address);
bool clkout_enabled();
void set_clkout_enabled(bool v);
uint16_t clkout_freq();
void set_clkout_freq(uint16_t freq);
bool dst_enabled();
void set_dst_enabled(bool v);
uint16_t clkout_freq();
dst_config_t config_dst();
void set_config_dst(dst_config_t v);
/* Recon app */
bool recon_autosave_freqs();
bool recon_autostart_recon();
+1 -1
View File
@@ -71,7 +71,7 @@ struct SharedMemory {
void set_baseband_ready() { baseband_ready = true; }
uint8_t volatile request_m4_performance_counter{0};
uint8_t volatile m4_cpu_usage{0};
uint8_t volatile m4_performance_counter{0};
uint16_t volatile m4_stack_usage{0};
uint32_t volatile m4_heap_usage{0};
uint16_t volatile m4_buffer_missed{0};
+12 -2
View File
@@ -31,9 +31,11 @@
#include "chprintf.h"
#include "irq_controls.hpp"
#include "string_format.hpp"
#include "usb_serial_io.h"
#include "usb_serial_device_to_host.h"
#include "rtc_time.hpp"
using namespace portapack;
using namespace rtc_time;
namespace ui {
@@ -433,7 +435,7 @@ void Labels::getWidgetName(std::string& result) {
/* LiveDateTime **********************************************************/
void LiveDateTime::on_tick_second() {
rtcGetTime(&RTCD1, &datetime);
rtc_time::now(datetime);
text = "";
if (!hide_clock) {
if (date_enabled) {
@@ -2118,7 +2120,15 @@ bool NumberField::on_key(const KeyEvent key) {
}
bool NumberField::on_encoder(const EncoderEvent delta) {
int32_t old_value = value();
set_value(value() + (delta * step));
if (on_wrap) {
if ((delta > 0) && (value() < old_value))
on_wrap(1);
else if ((delta < 0) && (value() > old_value))
on_wrap(-1);
}
return true;
}
+1
View File
@@ -783,6 +783,7 @@ class NumberField : public Widget {
public:
std::function<void(NumberField&)> on_select{};
std::function<void(int32_t)> on_change{};
std::function<void(int32_t)> on_wrap{};
using range_t = std::pair<int32_t, int32_t>;
+39 -16
View File
@@ -6,6 +6,8 @@
#include <string.h>
#include "string_format.hpp"
#include "file.hpp"
#include "utility.hpp"
#include "ui_external_items_menu_loader.hpp"
class UnTar {
public:
@@ -53,7 +55,6 @@ class UnTar {
}
static bool create_dir(char* pathname) {
char* p;
std::filesystem::filesystem_error r;
if (!isValidName(pathname)) return false;
@@ -65,18 +66,7 @@ class UnTar {
std::string dirnameStr = u'/' + pathname;
std::filesystem::path dirname = dirnameStr;
r = make_new_directory(dirname);
if (!r.ok()) {
/* On failure, try creating parent directory. */
p = strrchr(pathname, '/');
if (p != NULL) {
*p = '\0';
create_dir(pathname);
*p = '/';
r = make_new_directory(dirname);
}
}
r = ensure_directory(dirname);
return (r.ok());
}
@@ -98,6 +88,10 @@ class UnTar {
std::string binfile = "";
std::string fn = "";
int filesize;
uint32_t app_checksum;
bool app_file;
bool first_read;
bool corrupt_file{false};
for (;;) {
auto readres = a->read(buff, 512);
if (!readres.is_ok()) return "";
@@ -144,20 +138,49 @@ class UnTar {
delete_file(fn);
auto fres = f.open(fn, false, true);
if (!fres.value().ok()) return "";
app_file = (fn.substr(fn.size() - 5) == ".ppma");
first_read = true;
app_checksum = 0;
corrupt_file = false;
}
while (filesize > 0) {
readres = a->read(buff, 512);
if (!readres.is_ok()) return "";
if (!readres.is_ok()) {
corrupt_file = true;
break;
}
bytes_read = readres.value();
if (bytes_read < 512) {
return "";
corrupt_file = true;
break;
}
if (filesize < 512)
bytes_read = filesize;
if (app_file && first_read) {
// Check app file header for min version for checksum support
struct application_information_t* app_info = (struct application_information_t*)buff;
if (app_info->header_version < MIN_HEADER_VERSION_FOR_CHECKSUM)
app_file = false;
first_read = false;
}
if (app_file) {
app_checksum += simple_checksum((uint32_t)buff, bytes_read);
}
auto fwres = f.write(buff, bytes_read);
if (!fwres.is_ok()) return "";
if (!fwres.is_ok()) {
corrupt_file = true;
break;
}
filesize -= bytes_read;
f.sync();
if ((filesize == 0) && app_file && (app_checksum != EXT_APP_EXPECTED_CHECKSUM)) {
corrupt_file = true;
break;
}
}
if (corrupt_file) {
delete_file(fn);
return "";
}
}
return binfile;
+7
View File
@@ -235,3 +235,10 @@ std::string join(char c, std::initializer_list<std::string_view> strings) {
return result;
}
uint32_t simple_checksum(uint32_t buffer_address, uint32_t length) {
uint32_t checksum = 0;
for (uint32_t i = 0; i < length; i += 4)
checksum += *(uint32_t*)(buffer_address + i);
return checksum;
}
+2
View File
@@ -217,4 +217,6 @@ struct range_t {
std::string join(char c, std::initializer_list<std::string_view> strings);
uint32_t simple_checksum(uint32_t buffer_address, uint32_t length);
#endif /*__UTILITY_H__*/
@@ -0,0 +1,36 @@
from PIL import Image
import numpy as np
bitmap_data = [
0x30, 0x00,
0x30, 0x00,
0x30, 0x00,
0x30, 0x00,
0x30, 0x00,
0x30, 0x00,
0xFC, 0x00,
0xCE, 0x01,
0x86, 0x01,
0xFE, 0x01,
0x86, 0x31,
0x86, 0x49,
0xCE, 0x87,
0xFC, 0x84,
0xFC, 0x4B,
0x78, 0x30,
]
bitmap_size = (16, 16)
image_data = np.zeros((bitmap_size[1], bitmap_size[0]), dtype=np.uint8)
for y in range(bitmap_size[1]):
for x in range(bitmap_size[0]):
byte_index = (y * bitmap_size[0] + x) // 8
bit_index = x % 8
# bit_index = 7 - (x % 8)
pixel_value = (bitmap_data[byte_index] >> bit_index) & 1
image_data[y, x] = pixel_value * 255
image = Image.fromarray(image_data, 'L')
image.save('decoded.png')
+16
View File
@@ -104,6 +104,14 @@ for external_image_prefix in sys.argv[4:]:
external_application_image = patch_image(external_application_image, search_address, replace_address)
external_application_image[memory_location_header_position:memory_location_header_position+4] = replace_address.to_bytes(4, byteorder='little')
checksum = 0
for i in range(0, len(external_application_image), 4):
checksum += external_application_image[i] + (external_application_image[i + 1] << 8) + (external_application_image[i + 2] << 16) + (external_application_image[i + 3] << 24)
final_checksum = 0
checksum = (final_checksum - checksum) & 0xFFFFFFFF
external_application_image += checksum.to_bytes(4, 'little')
write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix))
continue
@@ -127,5 +135,13 @@ for external_image_prefix in sys.argv[4:]:
print("application {} can not exceed 32kb: {} bytes used".format(external_image_prefix, len(external_application_image)))
sys.exit(-1)
checksum = 0
for i in range(0, len(external_application_image), 4):
checksum += external_application_image[i] + (external_application_image[i + 1] << 8) + (external_application_image[i + 2] << 16) + (external_application_image[i + 3] << 24)
final_checksum = 0
checksum = (final_checksum - checksum) & 0xFFFFFFFF
external_application_image += checksum.to_bytes(4, 'little')
# write .ppma (portapack mayhem application)
write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix))
+19 -2
View File
@@ -74,7 +74,24 @@ for image in images:
padded_data = image['data'] + (spi_image_default_byte * pad_size)
spi_image += padded_data
if len(spi_image) > spi_size:
raise RuntimeError('SPI flash image size of %d exceeds device size of %d bytes' % (len(spi_image), spi_size))
if len(spi_image) > spi_size - 4:
raise RuntimeError('SPI flash image size of %d exceeds device size of %d bytes' % (len(spi_image) + 4, spi_size))
pad_size = spi_size - 4 - len(spi_image)
for i in range(pad_size):
spi_image += spi_image_default_byte
# quick "add up the words" checksum:
checksum = 0
for i in range(0, len(spi_image), 4):
checksum += (spi_image[i] + (spi_image[i + 1] << 8) + (spi_image[i + 2] << 16) + (spi_image[i + 3] << 24))
final_checksum = 0
checksum = (final_checksum - checksum) & 0xFFFFFFFF
spi_image += checksum.to_bytes(4, 'little')
write_image(spi_image, output_path)
percent_remaining = round(1000 * pad_size / spi_size) / 10;
print ("Space remaining in flash ROM:", pad_size, "bytes (", percent_remaining, "%)")
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
#
# Copyright (C) 2024 Mark Thompson
#
# This file is part of PortaPack.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
import sys
import struct
usage_message = """
PortaPack ROM image checksum checker
Usage: <command> <input-file>
"""
def read_image(path):
f = open(path, 'rb')
data = f.read()
f.close()
return data
def write_image(data, path):
f = open(path, 'wb')
f.write(data)
f.close()
if len(sys.argv) != 2:
print(usage_message)
sys.exit(-1)
image = read_image(sys.argv[1])
image = bytearray(image)
# simple "add up the words" checksum:
checksum = 0
for i in range(0, len(image), 4):
checksum += (image[i] + (image[i + 1] << 8) + (image[i + 2] << 16) + (image[i + 3] << 24))
checksum &= 0xFFFFFFFF
print ("Simple checksum =", checksum)
+1 -1
View File
@@ -1,2 +1,2 @@
[InternetShortcut]
URL=https://github.com/eried/portapack-mayhem/releases
URL=https://github.com/portapack-mayhem/mayhem-firmware/releases
+1 -1
View File
@@ -1,2 +1,2 @@
[InternetShortcut]
URL=https://github.com/eried/portapack-mayhem/wiki/Update-firmware
URL=https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware
+1 -1
View File
@@ -1,3 +1,3 @@
Plug HackRF+Portapack, set it in HackRF mode, launch flash_portapack_mayhem.bat
Read https://github.com/eried/portapack-mayhem/wiki/Update-firmware for more detailed information
Read https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware for more detailed information
+2 -2
View File
@@ -15,7 +15,7 @@ REM Check if the firmware file exists
if not exist portapack-h1_h2-mayhem.bin (
echo The firmware file "portapack-h1_h2-mayhem.bin" does not exist.
echo Please ensure that you have downloaded the latest release from:
echo https://github.com/eried/portapack-mayhem/releases/
echo https://github.com/portapack-mayhem/mayhem-firmware/releases/
echo.
pause
exit /b
@@ -23,6 +23,6 @@ if not exist portapack-h1_h2-mayhem.bin (
"utils/hackrf_update.exe" portapack-h1_h2-mayhem.bin
echo.
echo If your device never boot after flashing, please refer to https://github.com/eried/portapack-mayhem/wiki/Won't-boot
echo If your device never boot after flashing, please refer to https://github.com/portapack-mayhem/mayhem-firmware/wiki/Won't-boot
echo.
pause
@@ -1,2 +1,2 @@
[InternetShortcut]
URL=https://github.com/eried/portapack-havoc/wiki/GPS-Sim
URL=https://github.com/portapack-mayhem/mayhem-firmware/wiki/GPS-Sim
+1 -1
View File
@@ -2,7 +2,7 @@
Sample files below can be used with Replay app.
See [Wiki](https://github.com/eried/portapack-mayhem/wiki/C16-format) for more info over C16 file format.
See [Wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/C16-format) for more info over C16 file format.
| file | description | frequency |
| ------ | ------ | ------: |

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