mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-13 18:19:33 +00:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2a5fb166e | |||
| f59f5dfaa3 | |||
| 13f12227bd | |||
| c9db21063b | |||
| 125184f300 | |||
| 8068517808 | |||
| a4f6bbda5e | |||
| 8b2598fdac | |||
| c36fe78bd5 | |||
| bd8385464e | |||
| 8c996b5bc6 | |||
| d1901939e1 | |||
| 41ec11fb66 | |||
| df47490d85 | |||
| a2f6d14b15 | |||
| 6a6c6d6502 | |||
| 2d98c5d311 | |||
| 03b13f8ab0 | |||
| 6e74ad942f | |||
| 0f85f247b6 | |||
| ac1d350aaf | |||
| 5f8e1ef307 | |||
| aa5d4ad078 | |||
| 44d9572f5c | |||
| 3314001205 | |||
| ce1084abc7 | |||
| ea8563cb19 | |||
| b3c1c83677 | |||
| fcbc3b4d75 | |||
| 39e1a0ffe8 | |||
| 99bbd8805c | |||
| e7af85ffba | |||
| 75f3277102 | |||
| 216f8b64bf | |||
| bc301c5fdb | |||
| ae9d6de093 | |||
| 7a4c3184a1 | |||
| 58307aee9c | |||
| 409242507c | |||
| 0a8194fa10 | |||
| eaac40df3a | |||
| 58b488b840 | |||
| 1676702a09 | |||
| 3943848add | |||
| 831dbeaab5 | |||
| 49e719ded8 | |||
| c2d2409bff | |||
| 485f57d86b | |||
| 0e6f303eec | |||
| ad94ae05c1 | |||
| 89dc8c0463 | |||
| 77640c5cc0 | |||
| cdc8cfeb7d | |||
| 4740df2e2c | |||
| ec0f45a488 | |||
| 9d7e06c255 | |||
| 4b93e78dd9 | |||
| 5ced8c90d3 | |||
| 9d22711368 | |||
| 23e6295dd2 | |||
| bbd75ab7ef | |||
| 7508fee62c |
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+7
-11
@@ -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)
|
||||
@@ -83,5 +72,12 @@ set(HACKRF_CPLD_XSVF_PATH ${HACKRF_PATH}/firmware/cpld/sgpio_if/${HACKRF_CPLD_XS
|
||||
set(HACKRF_FIRMWARE_DFU_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_DFU_FILENAME})
|
||||
set(HACKRF_FIRMWARE_BIN_IMAGE ${hackrf_usb_BINARY_DIR}/${HACKRF_FIRMWARE_BIN_FILENAME})
|
||||
|
||||
find_program(CCACHE "ccache")
|
||||
if(CCACHE)
|
||||
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
|
||||
set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
|
||||
endif(CCACHE)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(firmware)
|
||||
|
||||
@@ -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
|
||||
|
||||
[](https://travis-ci.com/eried/portapack-mayhem) [](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/eried/portapack-mayhem/releases) [](https://github.com/eried/portapack-mayhem/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
[](https://github.com/portapack-mayhem/mayhem-firmware/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/portapack-mayhem/mayhem-firmware/releases) [](https://github.com/portapack-mayhem/mayhem-firmware/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](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
|
||||
|
||||

|
||||
|
||||
: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:
|
||||
|
||||

|
||||
|
||||
## Where is the latest version?
|
||||
|
||||
The current **stable release** is on the [](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 [](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).
|
||||
|
||||
[](https://github.com/eried/portapack-mayhem/graphs/contributors)
|
||||
[](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).
|
||||
|
||||
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
|
||||
@@ -19,9 +19,5 @@ RUN apk add --no-cache g++ gcc clang clang-static clang-dev llvm-dev llvm-static
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin && cd ~/bin && \
|
||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
||||
|
||||
CMD cd .. && cd build && \
|
||||
cmake .. && make ppfw
|
||||
|
||||
@@ -35,13 +35,6 @@ RUN mkdir /opt/build \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
@@ -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 :)
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -113,6 +113,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
||||
namespace app_settings {
|
||||
|
||||
enum class Mode : uint8_t {
|
||||
NO_RF = 0x00,
|
||||
RX = 0x01,
|
||||
TX = 0x02,
|
||||
RX_TX = 0x03, // Both TX/RX
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -111,18 +119,22 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
1536000 / 20, // Update vu-meter at 20Hz
|
||||
transmitter_model.channel_bandwidth(),
|
||||
0, // Gain is unused
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
|
||||
0, // AM
|
||||
0, // DSB
|
||||
0, // USB
|
||||
0 // LSB
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
8, // bits per sample
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
|
||||
transmitter_model.enable();
|
||||
|
||||
tx_view.set_transmitting(true);
|
||||
|
||||
if (tone_key_index == 0)
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
/*void SoundBoardView::show_infos() {
|
||||
@@ -227,6 +239,8 @@ SoundBoardView::SoundBoardView(
|
||||
//&text_title,
|
||||
//&text_duration,
|
||||
//&progressbar,
|
||||
&field_volume,
|
||||
&text_volume_disabled,
|
||||
&page_info,
|
||||
&check_loop,
|
||||
&check_random,
|
||||
@@ -252,6 +266,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 +295,6 @@ SoundBoardView::SoundBoardView(
|
||||
|
||||
SoundBoardView::~SoundBoardView() {
|
||||
stop();
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -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("");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -85,9 +110,8 @@ bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string&
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path) {
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path, ui::Painter& painter) {
|
||||
//
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
@@ -98,22 +122,22 @@ 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")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path).native();
|
||||
if (path.empty()) return;
|
||||
}
|
||||
bool FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
ui::Painter painter;
|
||||
if (endsWith(path, u".tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path, painter).native();
|
||||
}
|
||||
|
||||
if (path.empty() || !valid_firmware_file(path.c_str())) {
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, Styles::red, "BAD FIRMWARE FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
return false;
|
||||
}
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
@@ -126,6 +150,7 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
m0_halt();
|
||||
return true; // fixes compiler warning (line should not be reached due to halt)
|
||||
}
|
||||
|
||||
void FlashUtilityView::focus() {
|
||||
|
||||
@@ -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);
|
||||
@@ -41,6 +47,7 @@ class FlashUtilityView : public View {
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Flash Utility"; };
|
||||
bool flash_firmware(std::filesystem::path::string_type path);
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
@@ -55,9 +62,9 @@ class FlashUtilityView : public View {
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
void firmware_selected(std::filesystem::path::string_type path);
|
||||
void flash_firmware(std::filesystem::path::string_type path);
|
||||
|
||||
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
||||
};
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ void MicTXView::configure_baseband() {
|
||||
transmitting ? transmitter_model.channel_bandwidth() : 0,
|
||||
mic_gain_x10 / 10.0,
|
||||
shift_bits(), // to be used in dsp_modulate
|
||||
8, // bits per sample
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), sampling_rate),
|
||||
(mic_mod_index == MIC_MOD_AM),
|
||||
(mic_mod_index == MIC_MOD_DSB),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE u"SETTINGS/recon.cfg"
|
||||
|
||||
enum class recon_mode : uint8_t {
|
||||
Recon,
|
||||
Scanner,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -22,11 +22,12 @@
|
||||
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
void ViewWavView::update_scale(int32_t new_scale) {
|
||||
@@ -81,6 +82,8 @@ void ViewWavView::load_wav(std::filesystem::path file_path) {
|
||||
int16_t sample;
|
||||
uint32_t average;
|
||||
|
||||
wav_file_path = file_path;
|
||||
|
||||
text_filename.set(file_path.filename().string());
|
||||
auto ms_duration = wav_reader->ms_duration();
|
||||
text_duration.set(unit_auto_scale(ms_duration, 2, 3) + "s");
|
||||
@@ -117,9 +120,90 @@ void ViewWavView::reset_controls() {
|
||||
field_cursor_b.set_value(0);
|
||||
}
|
||||
|
||||
bool ViewWavView::is_active() {
|
||||
return (bool)replay_thread;
|
||||
}
|
||||
|
||||
void ViewWavView::stop() {
|
||||
if (is_active())
|
||||
replay_thread.reset();
|
||||
|
||||
audio::output::stop();
|
||||
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
ready_signal = false;
|
||||
}
|
||||
|
||||
void ViewWavView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
(void)return_code;
|
||||
|
||||
stop();
|
||||
progressbar.set_value(0);
|
||||
|
||||
if (return_code == ReplayThread::READ_ERROR)
|
||||
file_error();
|
||||
}
|
||||
|
||||
void ViewWavView::set_ready() {
|
||||
ready_signal = true;
|
||||
}
|
||||
|
||||
void ViewWavView::file_error() {
|
||||
nav_.display_modal("Error", "File read error.");
|
||||
}
|
||||
|
||||
void ViewWavView::start_playback() {
|
||||
uint32_t sample_rate;
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
stop();
|
||||
|
||||
if (!reader->open(wav_file_path)) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
|
||||
sample_rate = reader->sample_rate();
|
||||
|
||||
progressbar.set_max(reader->sample_count());
|
||||
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
read_size, buffer_count,
|
||||
&ready_signal,
|
||||
[](uint32_t return_code) {
|
||||
ReplayThreadDoneMessage message{return_code};
|
||||
EventDispatcher::send_message(message);
|
||||
});
|
||||
|
||||
baseband::set_audiotx_config(
|
||||
1536000 / 20, // Rate of sending progress updates
|
||||
0, // Transmit BW = 0 = not transmitting
|
||||
0, // Gain - unused
|
||||
8, // shift_bits_s16, default 8 bits - unused
|
||||
16, // bits per sample
|
||||
0, // tone key disabled
|
||||
false, // AM
|
||||
false, // DSB
|
||||
false, // USB
|
||||
false // LSB
|
||||
);
|
||||
baseband::set_sample_rate(sample_rate);
|
||||
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
void ViewWavView::on_playback_progress(const uint32_t progress) {
|
||||
progressbar.set_value(progress);
|
||||
}
|
||||
|
||||
ViewWavView::ViewWavView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_audio_tx);
|
||||
wav_reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
add_children({&labels,
|
||||
@@ -128,21 +212,26 @@ ViewWavView::ViewWavView(
|
||||
&text_title,
|
||||
&text_duration,
|
||||
&button_open,
|
||||
&button_play,
|
||||
&waveform,
|
||||
&progressbar,
|
||||
&field_pos_seconds,
|
||||
&field_pos_samples,
|
||||
&field_scale,
|
||||
&field_cursor_a,
|
||||
&field_cursor_b,
|
||||
&text_delta});
|
||||
&text_delta,
|
||||
&field_volume});
|
||||
|
||||
reset_controls();
|
||||
|
||||
button_open.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".WAV");
|
||||
open_view->on_changed = [this, &nav](std::filesystem::path file_path) {
|
||||
// Can't show new dialogs in an on_changed handler, so use continuation.
|
||||
nav.set_on_pop([this, &nav, file_path]() {
|
||||
if (!wav_reader->open(file_path)) {
|
||||
nav_.display_modal("Error", "Couldn't open file.");
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
if ((wav_reader->channels() != 1) || (wav_reader->bits_per_sample() != 16)) {
|
||||
@@ -155,6 +244,15 @@ ViewWavView::ViewWavView(
|
||||
};
|
||||
};
|
||||
|
||||
field_volume.set_value(field_volume.value());
|
||||
|
||||
button_play.on_select = [this, &nav](ImageButton&) {
|
||||
if (this->is_active())
|
||||
stop();
|
||||
else
|
||||
start_playback();
|
||||
};
|
||||
|
||||
field_scale.on_change = [this](int32_t value) {
|
||||
update_scale(value);
|
||||
};
|
||||
@@ -179,4 +277,9 @@ void ViewWavView::focus() {
|
||||
button_open.focus();
|
||||
}
|
||||
|
||||
ViewWavView::~ViewWavView() {
|
||||
stop();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -24,12 +24,15 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "io_wave.hpp"
|
||||
#include "spectrum_color_lut.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "replay_thread.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class ViewWavView : public View {
|
||||
public:
|
||||
ViewWavView(NavigationView& nav);
|
||||
~ViewWavView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
@@ -37,6 +40,9 @@ class ViewWavView : public View {
|
||||
std::string title() const override { return "WAV viewer"; };
|
||||
|
||||
private:
|
||||
app_settings::SettingsManager settings_{
|
||||
"wav_viewer", app_settings::Mode::NO_RF};
|
||||
|
||||
NavigationView& nav_;
|
||||
static constexpr uint32_t subsampling_factor = 8;
|
||||
|
||||
@@ -46,6 +52,20 @@ class ViewWavView : public View {
|
||||
void on_pos_changed();
|
||||
void load_wav(std::filesystem::path file_path);
|
||||
void reset_controls();
|
||||
bool is_active();
|
||||
void stop();
|
||||
void handle_replay_thread_done(const uint32_t return_code);
|
||||
void set_ready();
|
||||
void file_error();
|
||||
void start_playback();
|
||||
void on_playback_progress(const uint32_t progress);
|
||||
|
||||
std::filesystem::path wav_file_path{};
|
||||
std::unique_ptr<ReplayThread> replay_thread{};
|
||||
bool ready_signal{false};
|
||||
const size_t read_size{2048};
|
||||
const size_t buffer_count{3};
|
||||
const uint32_t progress_interval_samples{1536000 / 20};
|
||||
|
||||
std::unique_ptr<WAVFileReader> wav_reader{};
|
||||
|
||||
@@ -60,10 +80,11 @@ class ViewWavView : public View {
|
||||
{{0 * 8, 1 * 16}, "Samplerate:", Color::light_grey()},
|
||||
{{0 * 8, 2 * 16}, "Title:", Color::light_grey()},
|
||||
{{0 * 8, 3 * 16}, "Duration:", Color::light_grey()},
|
||||
{{0 * 8, 11 * 16}, "Position: s Scale:", Color::light_grey()},
|
||||
{{0 * 8, 12 * 16}, "Cursor A:", Color::dark_cyan()},
|
||||
{{0 * 8, 13 * 16}, "Cursor B:", Color::dark_magenta()},
|
||||
{{0 * 8, 14 * 16}, "Delta:", Color::light_grey()}};
|
||||
{{0 * 8, 12 * 16}, "Position: s Scale:", Color::light_grey()},
|
||||
{{0 * 8, 13 * 16}, "Cursor A:", Color::dark_cyan()},
|
||||
{{0 * 8, 14 * 16}, "Cursor B:", Color::dark_magenta()},
|
||||
{{0 * 8, 15 * 16}, "Delta:", Color::light_grey()},
|
||||
{{24 * 8, 18 * 16}, "Vol:", Color::light_grey()}};
|
||||
|
||||
Text text_filename{
|
||||
{5 * 8, 0 * 16, 12 * 8, 16},
|
||||
@@ -80,6 +101,13 @@ class ViewWavView : public View {
|
||||
Button button_open{
|
||||
{24 * 8, 8, 6 * 8, 2 * 16},
|
||||
"Open"};
|
||||
ImageButton button_play{
|
||||
{24 * 8, 17 * 16, 2 * 8, 1 * 16},
|
||||
&bitmap_play,
|
||||
Color::green(),
|
||||
Color::black()};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 18 * 16}};
|
||||
|
||||
Waveform waveform{
|
||||
{0, 5 * 16, 240, 64},
|
||||
@@ -89,34 +117,29 @@ class ViewWavView : public View {
|
||||
false,
|
||||
Color::white()};
|
||||
|
||||
ProgressBar progressbar{
|
||||
{0 * 8, 11 * 16, 30 * 8, 8}};
|
||||
|
||||
NumberField field_pos_seconds{
|
||||
{9 * 8, 11 * 16},
|
||||
{9 * 8, 12 * 16},
|
||||
3,
|
||||
{0, 999},
|
||||
1,
|
||||
' '};
|
||||
NumberField field_pos_samples{
|
||||
{14 * 8, 11 * 16},
|
||||
{14 * 8, 12 * 16},
|
||||
6,
|
||||
{0, 999999},
|
||||
1,
|
||||
'0'};
|
||||
NumberField field_scale{
|
||||
{28 * 8, 11 * 16},
|
||||
{28 * 8, 12 * 16},
|
||||
2,
|
||||
{1, 40},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_cursor_a{
|
||||
{9 * 8, 12 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
' ',
|
||||
true};
|
||||
|
||||
NumberField field_cursor_b{
|
||||
{9 * 8, 13 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
@@ -124,9 +147,40 @@ class ViewWavView : public View {
|
||||
' ',
|
||||
true};
|
||||
|
||||
NumberField field_cursor_b{
|
||||
{9 * 8, 14 * 16},
|
||||
3,
|
||||
{0, 239},
|
||||
1,
|
||||
' ',
|
||||
true};
|
||||
|
||||
Text text_delta{
|
||||
{6 * 8, 14 * 16, 30 * 8, 16},
|
||||
{7 * 8, 15 * 16, 30 * 8, 16},
|
||||
"-"};
|
||||
|
||||
MessageHandlerRegistration message_handler_replay_thread_error{
|
||||
Message::ID::ReplayThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
|
||||
this->handle_replay_thread_done(message.return_code);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_fifo_signal{
|
||||
Message::ID::RequestSignal,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||
this->set_ready();
|
||||
}
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const TXProgressMessage*>(p);
|
||||
this->on_playback_progress(message.progress);
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -211,6 +211,7 @@ void set_audiotx_config(
|
||||
const float deviation_hz,
|
||||
const float audio_gain,
|
||||
uint8_t audio_shift_bits_s16,
|
||||
uint8_t bits_per_sample,
|
||||
const uint32_t tone_key_delta,
|
||||
const bool am_enabled,
|
||||
const bool dsb_enabled,
|
||||
@@ -221,6 +222,7 @@ void set_audiotx_config(
|
||||
deviation_hz,
|
||||
audio_gain,
|
||||
audio_shift_bits_s16,
|
||||
bits_per_sample,
|
||||
tone_key_delta,
|
||||
(float)persistent_memory::tone_mix() / 100.0f,
|
||||
am_enabled,
|
||||
|
||||
@@ -63,7 +63,7 @@ void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duratio
|
||||
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
|
||||
void kill_tone();
|
||||
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
|
||||
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
|
||||
void set_audiotx_config(const uint32_t divider, const float deviation_hz, const float audio_gain, uint8_t audio_shift_bits_s16, uint8_t bits_per_sample, const uint32_t tone_key_delta, const bool am_enabled, const bool dsb_enabled, const bool usb_enabled, const bool lsb_enabled);
|
||||
void set_fifo_data(const int8_t* data);
|
||||
void set_pitch_rssi(int32_t avg, bool enabled);
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
|
||||
@@ -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[] = {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
Vendored
+2
-2
@@ -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 */
|
||||
Vendored
+2
-2
@@ -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 */
|
||||
+81
-22
@@ -13,8 +13,8 @@ typedef uint8_t byte;
|
||||
typedef uint32_t word;
|
||||
|
||||
byte SPEED = 2;
|
||||
byte MAXLIFES = 5;
|
||||
byte LIFES = START_LIFES;
|
||||
byte MAXLIFES = 20;
|
||||
size_t LIFES = START_LIFES;
|
||||
byte GAMEWIN = 0;
|
||||
byte GAMEOVER = 0;
|
||||
byte DEMO = 1;
|
||||
@@ -25,6 +25,9 @@ byte GAMEPAUSED = 0;
|
||||
|
||||
byte PACMANFALLBACK = 0;
|
||||
|
||||
bool cheat_level = false;
|
||||
bool cheat_lifes = false;
|
||||
|
||||
#include "DrawIndexedMap.h"
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -205,11 +208,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 +279,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 +851,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
|
||||
@@ -887,14 +904,15 @@ class Playfield {
|
||||
|
||||
void PackmanDied() { // Noooo... PACMAN DIED :(
|
||||
|
||||
if (LIFES <= 0) {
|
||||
if (LIFES <= 0 && !cheat_lifes) {
|
||||
GAMEOVER = 1;
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
DEMO = 1;
|
||||
Init();
|
||||
} else {
|
||||
LIFES--;
|
||||
if (!cheat_lifes)
|
||||
LIFES--;
|
||||
|
||||
_inited = true;
|
||||
_state = ReadyState;
|
||||
@@ -920,8 +938,14 @@ class Playfield {
|
||||
_icons[13 - i] = BONUSICON + i;
|
||||
}
|
||||
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (!cheat_lifes) {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < 14; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1163,8 +1187,13 @@ class Playfield {
|
||||
if (GAMEWIN == 1) {
|
||||
GAMEWIN = 0;
|
||||
} else {
|
||||
LEVEL = START_LEVEL;
|
||||
LIFES = START_LIFES;
|
||||
if (!cheat_level) {
|
||||
LEVEL = START_LEVEL;
|
||||
}
|
||||
if (!cheat_lifes) {
|
||||
LIFES = START_LIFES;
|
||||
}
|
||||
|
||||
ACTUALBONUS = 0; // actual bonus icon
|
||||
ACTIVEBONUS = 0; // status of bonus
|
||||
|
||||
@@ -1201,8 +1230,14 @@ class Playfield {
|
||||
}
|
||||
|
||||
// SET Lifes icons
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
if (cheat_lifes) {
|
||||
for (byte i = 0; i < 14; i++) { // cuz 14 lives full fills PP's screen
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
} else {
|
||||
for (byte i = 0; i < LIFES; i++) {
|
||||
_icons[0 + i] = PACMANICON;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw LIFE and BONUS Icons
|
||||
@@ -1229,21 +1264,45 @@ class Playfield {
|
||||
}
|
||||
|
||||
void Step() {
|
||||
int16_t keys = 0;
|
||||
|
||||
if (GAMEWIN == 1) {
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
LEVEL++;
|
||||
Init();
|
||||
}
|
||||
|
||||
// Start GAME
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) {
|
||||
if (but_A && DEMO == 1 && GAMEPAUSED == 0) { // start
|
||||
but_A = false;
|
||||
DEMO = 0;
|
||||
Init();
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // Or PAUSE GAME
|
||||
DEMO = 0;
|
||||
} else if (but_A && DEMO == 0 && GAMEPAUSED == 0) { // pause
|
||||
but_A = false;
|
||||
GAMEPAUSED = 1;
|
||||
} else if (but_LEFT && DEMO == 1 && GAMEPAUSED == 0) { // -level
|
||||
cheat_level = true;
|
||||
but_LEFT = false;
|
||||
if (LEVEL > 1) {
|
||||
LEVEL--;
|
||||
}
|
||||
Init();
|
||||
} else if (but_RIGHT && DEMO == 1 && GAMEPAUSED == 0) { // +level
|
||||
cheat_level = true;
|
||||
but_RIGHT = false;
|
||||
if (LEVEL < 255) {
|
||||
LEVEL++;
|
||||
}
|
||||
Init();
|
||||
} else if (but_UP && DEMO == 1 && GAMEPAUSED == 0) { // full of lifes
|
||||
cheat_lifes = true;
|
||||
but_UP = false;
|
||||
Init();
|
||||
} else if (but_DOWN && DEMO == 1 && GAMEPAUSED == 0) { // reset
|
||||
cheat_level = false;
|
||||
cheat_lifes = false;
|
||||
but_DOWN = false;
|
||||
LIFES = START_LIFES;
|
||||
Init();
|
||||
}
|
||||
|
||||
if (GAMEPAUSED && but_A && DEMO == 0) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "utility.hpp"
|
||||
|
||||
uint8_t Debounce::state() {
|
||||
bool v = state_to_report_;
|
||||
uint8_t v = state_to_report_;
|
||||
simulated_pulse_ = false;
|
||||
return v;
|
||||
}
|
||||
@@ -149,3 +149,24 @@ bool Debounce::feed(const uint8_t bit) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t EncoderDebounce::state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
// Returns TRUE if encoder position phase bits changed (after debouncing)
|
||||
bool EncoderDebounce::feed(const uint8_t phase_bits) {
|
||||
history_ = (history_ << 2) | phase_bits;
|
||||
|
||||
// Has input been constant for 4 ticks? (phase_bits * 01010101b should be 0x00, 0x55, 0xAA, or 0xFF)
|
||||
if (history_ == (phase_bits * 0x55)) {
|
||||
// Has the debounced input value changed?
|
||||
if (state_ != phase_bits) {
|
||||
state_ = phase_bits;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unstable input, or no change
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -64,4 +64,16 @@ class Debounce {
|
||||
bool long_press_occurred_{false}; // TRUE when button is being held down and LONG_PRESS_DELAY has been reached (only when long_press_enabled)
|
||||
};
|
||||
|
||||
class EncoderDebounce {
|
||||
public:
|
||||
bool feed(const uint8_t phase_bits); // returns TRUE if state changed after debouncing
|
||||
|
||||
uint8_t state(); // returns debounced phase bits from encoder
|
||||
|
||||
private:
|
||||
uint8_t history_{0}; // shift register of previous reads from encoder
|
||||
|
||||
uint8_t state_{0}; // actual encoder output state (after debounce logic)
|
||||
};
|
||||
|
||||
#endif /*__DEBOUNCE_H__*/
|
||||
|
||||
@@ -94,13 +94,8 @@ static const int8_t transition_map[][16] = {
|
||||
},
|
||||
};
|
||||
|
||||
int_fast8_t Encoder::update(
|
||||
const uint_fast8_t phase_0,
|
||||
const uint_fast8_t phase_1) {
|
||||
state <<= 1;
|
||||
state |= phase_0;
|
||||
state <<= 1;
|
||||
state |= phase_1;
|
||||
int_fast8_t Encoder::update(const uint_fast8_t phase_bits) {
|
||||
state = (state << 2) | phase_bits;
|
||||
|
||||
// dial sensitivity setting is stored in pmem
|
||||
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
|
||||
|
||||
@@ -26,9 +26,7 @@
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
int_fast8_t update(
|
||||
const uint_fast8_t phase_0,
|
||||
const uint_fast8_t phase_1);
|
||||
int_fast8_t update(const uint_fast8_t phase_bits);
|
||||
|
||||
private:
|
||||
uint_fast8_t state{0};
|
||||
|
||||
@@ -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 */
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
@@ -43,7 +43,7 @@ static Thread* thread_controls_event = NULL;
|
||||
|
||||
// Index with the Switch enum.
|
||||
static std::array<Debounce, 6> switch_debounce;
|
||||
static std::array<Debounce, 2> encoder_debounce;
|
||||
static EncoderDebounce encoder_debounce;
|
||||
|
||||
static_assert(std::size(switch_debounce) == toUType(Switch::Dfu) + 1);
|
||||
|
||||
@@ -162,16 +162,12 @@ static bool switches_update(const uint8_t raw) {
|
||||
}
|
||||
|
||||
static bool encoder_update(const uint8_t raw) {
|
||||
bool encoder_changed = false;
|
||||
|
||||
encoder_changed |= encoder_debounce[0].feed((raw >> 6) & 0x01);
|
||||
encoder_changed |= encoder_debounce[1].feed((raw >> 7) & 0x01);
|
||||
|
||||
return encoder_changed;
|
||||
// TODO: swap +1/-1 directions in encoder.update() to avoid needless swizzing of phase bits here
|
||||
return encoder_debounce.feed(((raw >> 7) & 0x01) | ((raw >> 5) & 0x02));
|
||||
}
|
||||
|
||||
static bool encoder_read() {
|
||||
auto delta = encoder.update(encoder_debounce[0].state(), encoder_debounce[1].state());
|
||||
auto delta = encoder.update(encoder_debounce.state());
|
||||
|
||||
if (injected_encoder > 0) {
|
||||
if (injected_encoder == 1) delta = -1;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -192,11 +184,13 @@ bool set_tuning_frequency(const rf::Frequency frequency) {
|
||||
if (tuning_config.is_valid()) {
|
||||
first_if.disable();
|
||||
|
||||
// Program first local oscillator frequency (if there is one) into RFFC507x
|
||||
if (tuning_config.first_lo_frequency) {
|
||||
first_if.set_frequency(tuning_config.first_lo_frequency);
|
||||
first_if.enable();
|
||||
}
|
||||
|
||||
// Program second local oscillator frequency into MAX283x
|
||||
const auto result_second_if = second_if->set_frequency(tuning_config.second_lo_frequency);
|
||||
|
||||
rf_path.set_band(tuning_config.rf_path_band);
|
||||
|
||||
@@ -39,8 +39,8 @@ enum class Direction {
|
||||
|
||||
namespace path {
|
||||
|
||||
constexpr FrequencyRange band_low{0, 2170000000};
|
||||
constexpr FrequencyRange band_high{2740000000, 7250000000};
|
||||
constexpr FrequencyRange band_low{0, 2170'000'000};
|
||||
constexpr FrequencyRange band_high{2740'000'000, 7250'000'000};
|
||||
constexpr FrequencyRange band_mid{band_low.maximum, band_high.minimum};
|
||||
|
||||
enum class Band {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -26,44 +26,43 @@
|
||||
namespace tuning {
|
||||
namespace config {
|
||||
|
||||
namespace {
|
||||
|
||||
// Low band <2170 Mhz:
|
||||
constexpr rf::Frequency low_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
return 2650000000 - (target_frequency / 7);
|
||||
}
|
||||
|
||||
constexpr rf::Frequency high_band_second_lo_regions_2_and_3(const rf::Frequency target_frequency) {
|
||||
return (target_frequency < 5100000000)
|
||||
? (2350000000 + ((target_frequency - 3600000000) / 5))
|
||||
: (2500000000 + ((target_frequency - 5100000000) / 9));
|
||||
}
|
||||
|
||||
constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
return (target_frequency < 3600000000)
|
||||
? (2170000000 + (((target_frequency - 2740000000) * 57) / 86))
|
||||
: high_band_second_lo_regions_2_and_3(target_frequency);
|
||||
return 2650'000'000 - (target_frequency / 7);
|
||||
}
|
||||
|
||||
Config low_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency + low_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = first_lo_frequency - target_frequency;
|
||||
const rf::Frequency second_lo_frequency = low_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency first_lo_frequency = target_frequency + second_lo_frequency;
|
||||
const bool mixer_invert = true;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Low, mixer_invert};
|
||||
}
|
||||
|
||||
// Mid band 2170-2740 Mhz:
|
||||
Config mid_band(const rf::Frequency target_frequency) {
|
||||
return {0, target_frequency, rf::path::Band::Mid, false};
|
||||
const rf::Frequency second_lo_frequency = target_frequency;
|
||||
const rf::Frequency first_lo_frequency = 0;
|
||||
const bool mixer_invert = false;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::Mid, mixer_invert};
|
||||
}
|
||||
|
||||
// High band >2740 Mhz:
|
||||
constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target_frequency) {
|
||||
if (target_frequency < 3600'000'000)
|
||||
return (2170'000'000 + (((target_frequency - 2740'000'000) * 57) / 86));
|
||||
else if (target_frequency < 5100'000'000)
|
||||
return (2350'000'000 + ((target_frequency - 3600'000'000) / 5));
|
||||
else
|
||||
return (2500'000'000 + ((target_frequency - 5100'000'000) / 9));
|
||||
}
|
||||
|
||||
Config high_band(const rf::Frequency target_frequency) {
|
||||
const rf::Frequency first_lo_frequency = target_frequency - high_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency second_lo_frequency = target_frequency - first_lo_frequency;
|
||||
const rf::Frequency second_lo_frequency = high_band_second_lo_frequency(target_frequency);
|
||||
const rf::Frequency first_lo_frequency = target_frequency - second_lo_frequency;
|
||||
const bool mixer_invert = false;
|
||||
return {first_lo_frequency, second_lo_frequency, rf::path::Band::High, mixer_invert};
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
Config create(const rf::Frequency target_frequency) {
|
||||
/* TODO: This is some lame code. */
|
||||
if (rf::path::band_low.contains(target_frequency)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -210,8 +315,15 @@ SystemStatusView::SystemStatusView(
|
||||
refresh();
|
||||
};
|
||||
|
||||
toggle_stealth.on_change = [this](bool v) {
|
||||
toggle_stealth.on_change = [this, &nav](bool v) {
|
||||
pmem::set_stealth_mode(v);
|
||||
if (nav.is_valid() && v) {
|
||||
nav.display_modal(
|
||||
"Stealth",
|
||||
"You just enabled stealth mode.\n"
|
||||
"When you transmit,\n"
|
||||
"screen will turn off;\n");
|
||||
}
|
||||
refresh();
|
||||
};
|
||||
|
||||
@@ -398,8 +510,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 +519,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 +554,16 @@ InformationView::InformationView(
|
||||
<ime});
|
||||
|
||||
#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 ERR");
|
||||
version.set_style(&Styles::red);
|
||||
}
|
||||
|
||||
ltime.set_style(&style_infobar);
|
||||
refresh();
|
||||
set_dirty();
|
||||
@@ -463,12 +575,27 @@ 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 {
|
||||
return view_stack.size() == 1;
|
||||
}
|
||||
|
||||
bool NavigationView::is_valid() const {
|
||||
return view_stack.size() != 0; // work around to check if nav is valid, not elegant i know. so TODO
|
||||
}
|
||||
|
||||
View* NavigationView::push_view(std::unique_ptr<View> new_view) {
|
||||
free_view();
|
||||
|
||||
@@ -497,6 +624,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 +691,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 +740,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 +752,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 +775,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 +840,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 +850,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 +858,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;
|
||||
}
|
||||
|
||||
@@ -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{};
|
||||
@@ -69,6 +94,7 @@ class NavigationView : public View {
|
||||
NavigationView& operator=(NavigationView&&) = delete;
|
||||
|
||||
bool is_top() const;
|
||||
bool is_valid() const;
|
||||
|
||||
template <class T, class... Args>
|
||||
T* push(Args&&... args) {
|
||||
@@ -85,6 +111,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 +126,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 +287,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 +357,8 @@ class SystemView : public View {
|
||||
void toggle_overlay();
|
||||
void paint_overlay();
|
||||
|
||||
NavigationView* get_navigation_view();
|
||||
|
||||
private:
|
||||
uint8_t overlay_active{0};
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
+44
-36
@@ -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();
|
||||
}
|
||||
}
|
||||
+18
-6
@@ -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();
|
||||
@@ -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,35 +146,21 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check file extensions
|
||||
if (strEndsWith(path.native(), u".ppfw.tar")) {
|
||||
// extract tar
|
||||
chprintf(chp, "Extracting TAR file.\r\n");
|
||||
auto res = UnTar::untar(
|
||||
path.native(), [chp](const std::string fileName) {
|
||||
chprintf(chp, fileName.c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
});
|
||||
if (res.empty()) {
|
||||
chprintf(chp, "error bad TAR file.\r\n");
|
||||
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");
|
||||
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(false);
|
||||
|
||||
// call nav with flash
|
||||
auto open_view = nav->push<ui::FlashUtilityView>();
|
||||
chprintf(chp, "Flashing started\r\n");
|
||||
chThdSleepMilliseconds(150); // to give display some time to paint the screen
|
||||
if (!open_view->flash_firmware(path.native())) {
|
||||
chprintf(chp, "error\r\n");
|
||||
}
|
||||
chprintf(chp, "Flashing: ");
|
||||
chprintf(chp, path.string().c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
chThdSleepMilliseconds(50);
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.native().c_str(), (path.native().length() + 1) * 2);
|
||||
m4_request_shutdown();
|
||||
chThdSleepMilliseconds(50);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
m0_halt();
|
||||
}
|
||||
|
||||
static void cmd_screenshot(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
@@ -245,6 +192,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 +205,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 +225,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 +298,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 +325,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 +368,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 +381,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 +397,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 +635,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 +893,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 +1033,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 +1056,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
|
||||
@@ -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>
|
||||
|
||||
@@ -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__
|
||||
@@ -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};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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" */
|
||||
|
||||
@@ -30,18 +30,36 @@
|
||||
void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) return;
|
||||
|
||||
int32_t audio_sample_m;
|
||||
|
||||
// Zero-order hold (poop)
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
resample_acc += resample_inc;
|
||||
if (resample_acc >= 0x10000) {
|
||||
resample_acc -= 0x10000;
|
||||
if (stream) {
|
||||
stream->read(&audio_sample, 1);
|
||||
bytes_read++;
|
||||
audio_sample = 0;
|
||||
stream->read(&audio_sample, bytes_per_sample); // assumes little endian when reading 1 byte
|
||||
samples_read++;
|
||||
}
|
||||
}
|
||||
|
||||
sample = tone_gen.process(audio_sample - 0x80);
|
||||
if (bytes_per_sample == 1) {
|
||||
sample = audio_sample - 0x80;
|
||||
audio_sample_m = sample * 256;
|
||||
} else {
|
||||
audio_sample_m = audio_sample;
|
||||
}
|
||||
|
||||
// Output to speaker too
|
||||
if (!tone_key_enabled) {
|
||||
uint32_t imod32 = i & (AUDIO_OUTPUT_BUFFER_SIZE - 1);
|
||||
audio_data[imod32] = audio_sample_m;
|
||||
if (imod32 == (AUDIO_OUTPUT_BUFFER_SIZE - 1))
|
||||
audio_output.write_unprocessed(audio_buffer);
|
||||
}
|
||||
|
||||
sample = tone_gen.process(sample);
|
||||
|
||||
// FM
|
||||
delta = sample * fm_delta;
|
||||
@@ -59,7 +77,7 @@ void AudioTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (progress_samples >= progress_interval_samples) {
|
||||
progress_samples -= progress_interval_samples;
|
||||
|
||||
txprogress_message.progress = bytes_read; // Inform UI about progress
|
||||
txprogress_message.progress = samples_read; // Inform UI about progress
|
||||
txprogress_message.done = false;
|
||||
shared_memory.application_queue.push(txprogress_message);
|
||||
}
|
||||
@@ -73,7 +91,7 @@ void AudioTXProcessor::on_message(const Message* const message) {
|
||||
|
||||
case Message::ID::ReplayConfig:
|
||||
configured = false;
|
||||
bytes_read = 0;
|
||||
samples_read = 0;
|
||||
replay_config(*reinterpret_cast<const ReplayConfigMessage*>(message));
|
||||
break;
|
||||
|
||||
@@ -95,6 +113,11 @@ 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;
|
||||
bytes_per_sample = message.bits_per_sample / 8;
|
||||
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) {
|
||||
|
||||
@@ -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:
|
||||
@@ -44,14 +48,21 @@ class AudioTXProcessor : public BasebandProcessor {
|
||||
uint32_t resample_inc{}, resample_acc{};
|
||||
uint32_t fm_delta{0};
|
||||
uint32_t phase{0}, sphase{0};
|
||||
uint8_t audio_sample{};
|
||||
uint32_t audio_sample{};
|
||||
int32_t sample{0}, delta{};
|
||||
int8_t re{0}, im{0};
|
||||
int8_t bytes_per_sample{1};
|
||||
int16_t audio_sample_s16{};
|
||||
|
||||
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};
|
||||
uint32_t samples_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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -118,6 +118,8 @@ class Message {
|
||||
SubGhzFPRxConfigure = 60,
|
||||
WeatherData = 61,
|
||||
SubGhzDData = 62,
|
||||
GPSPosData = 63,
|
||||
OrientationData = 64,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -929,6 +931,7 @@ class AudioTXConfigMessage : public Message {
|
||||
const float deviation_hz,
|
||||
const float audio_gain,
|
||||
const uint8_t audio_shift_bits_s16,
|
||||
const uint8_t bits_per_sample,
|
||||
const uint32_t tone_key_delta,
|
||||
const float tone_key_mix_weight,
|
||||
const bool am_enabled,
|
||||
@@ -940,6 +943,7 @@ class AudioTXConfigMessage : public Message {
|
||||
deviation_hz(deviation_hz),
|
||||
audio_gain(audio_gain),
|
||||
audio_shift_bits_s16(audio_shift_bits_s16),
|
||||
bits_per_sample(bits_per_sample),
|
||||
tone_key_delta(tone_key_delta),
|
||||
tone_key_mix_weight(tone_key_mix_weight),
|
||||
am_enabled(am_enabled),
|
||||
@@ -952,6 +956,7 @@ class AudioTXConfigMessage : public Message {
|
||||
const float deviation_hz;
|
||||
const float audio_gain;
|
||||
const uint8_t audio_shift_bits_s16;
|
||||
const uint8_t bits_per_sample;
|
||||
const uint32_t tone_key_delta;
|
||||
const float tone_key_mix_weight;
|
||||
const bool am_enabled;
|
||||
@@ -1301,4 +1306,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__*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user