mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-01 20:39:08 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| fe88760766 | |||
| 75ece38725 | |||
| b53032a8a5 | |||
| e7f8952ece | |||
| 82a6ae0791 | |||
| d303098e35 | |||
| 1a69ce2d97 | |||
| eedebe1c52 | |||
| 9e61f80809 | |||
| ff591c68a0 | |||
| 8761b9d7e0 | |||
| 1b5125b0a8 | |||
| 9ba885361d | |||
| afa34d83b2 | |||
| d93c914dd3 | |||
| 715a2dd448 | |||
| 58bf60695d | |||
| fbe7954f2e | |||
| d122a8fc3b | |||
| 3998dc124a | |||
| 3d2da9c0db | |||
| 3189d3af4c | |||
| 1b3a99cc5e | |||
| 935c43e88f | |||
| 2893c031ab | |||
| 94cdb16ca9 |
+1
-1
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
|
||||
custom: # https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
|
||||
|
||||
@@ -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,15 +28,20 @@ jobs:
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Get version date
|
||||
id: version_date
|
||||
run: echo "::set-output name=date::n_$(date +'%y%m%d')"
|
||||
run: echo "date=n_$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: next
|
||||
#ref: next
|
||||
# The branch, tag or SHA to checkout. When checking out the repository that
|
||||
# triggered a workflow, this defaults to the reference or SHA for that event.
|
||||
# Otherwise, uses the default branch.
|
||||
# https://github.com/actions/checkout
|
||||
# So scheduled runs will use the default branch (next) but its now possible to trigger a workflow from another branch
|
||||
submodules: true
|
||||
- name: Git Sumbodule Update
|
||||
run: |
|
||||
@@ -49,10 +54,10 @@ jobs:
|
||||
run: docker run -e VERSION_STRING=${{ steps.version_date.outputs.date }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
@@ -61,16 +66,16 @@ jobs:
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version_date.outputs.date }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py)
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "::set-output name=content::$CHANGELOG"
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
@@ -88,6 +93,16 @@ jobs:
|
||||
${{ steps.changelog.outputs.content }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
- name: Upload Firmware TAR Asset
|
||||
id: upload-firmware-tar-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
id: upload-firmware-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
|
||||
@@ -9,22 +9,27 @@ jobs:
|
||||
steps:
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: next
|
||||
#ref: next
|
||||
# The branch, tag or SHA to checkout. When checking out the repository that
|
||||
# triggered a workflow, this defaults to the reference or SHA for that event.
|
||||
# Otherwise, uses the default branch.
|
||||
# https://github.com/actions/checkout
|
||||
# So scheduled runs will use the default branch (next) but its now possible to trigger a workflow from another branch
|
||||
submodules: true
|
||||
- name: Git Sumbodule Update
|
||||
run: |
|
||||
git submodule update --init --recursive
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "::set-output name=version::$(cat .github/workflows/version.txt)"
|
||||
run: echo "version=$(cat .github/workflows/version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Get past version
|
||||
id: past_version
|
||||
run: echo "::set-output name=past_version::$(cat .github/workflows/past_version.txt)"
|
||||
run: echo "past_version=$(cat .github/workflows/past_version.txt)" >> $GITHUB_OUTPUT
|
||||
- name: Build the Docker image
|
||||
run: docker build -t portapack-dev -f dockerfile-nogit . --tag my-image-name:$(date +%s)
|
||||
- name: Make build folder
|
||||
@@ -33,10 +38,10 @@ jobs:
|
||||
run: docker run -e VERSION_STRING=${{ steps.version.outputs.version }} -i -v ${{ github.workspace }}:/havoc portapack-dev
|
||||
- name: Create Small SD Card ZIP - No World Map
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard-no-map.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard-no-map.zip . && cd ..
|
||||
- name: Download world map
|
||||
run: |
|
||||
wget https://github.com/eried/portapack-mayhem/releases/download/world_map/world_map.zip
|
||||
wget https://github.com/portapack-mayhem/mayhem-firmware/releases/download/world_map/world_map.zip
|
||||
- name: Unzip world map
|
||||
run: |
|
||||
unzip world_map.zip -d sdcard/ADSB
|
||||
@@ -45,16 +50,16 @@ jobs:
|
||||
zip -j firmware.zip build/firmware/portapack-h1_h2-mayhem.bin && cd flashing && zip -r ../firmware.zip *
|
||||
- name: Create SD Card ZIP
|
||||
run: |
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && zip -r sdcard.zip sdcard
|
||||
mkdir -p sdcard/FIRMWARE && cp build/firmware/portapack-h1_h2-mayhem.bin sdcard/FIRMWARE/portapack-mayhem_${{ steps.version.outputs.version }}.bin && mkdir -p sdcard/APPS && cp build/firmware/application/*.ppma sdcard/APPS && cd sdcard && zip -r ../sdcard.zip . && cd ..
|
||||
- name: Create changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
echo "::set-output name=content::$CHANGELOG"
|
||||
{
|
||||
echo 'content<<EOF'
|
||||
python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }}
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
id: changelog
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
@@ -66,21 +71,32 @@ jobs:
|
||||
release_name: Mayhem firmware ${{ steps.version.outputs.version }}
|
||||
body: |
|
||||
**Stable release - ${{ steps.version.outputs.version }}**
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/eried/portapack-mayhem/blob/master/README.md) for details.
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). Please check the [readme](https://github.com/portapack-mayhem/mayhem-firmware/blob/master/README.md) for details.
|
||||
## Release notes
|
||||
### Revision (${{ steps.version.outputs.version }}):
|
||||
${{ steps.changelog.outputs.content }}
|
||||
|
||||
**Full Changelog**: https://github.com/eried/portapack-mayhem/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
||||
**Full Changelog**: https://github.com/portapack-mayhem/mayhem-firmware/compare/${{ steps.past_version.outputs.past_version }}...${{ steps.version.outputs.version }}
|
||||
|
||||
## Installation
|
||||
Check the [wiki](https://github.com/eried/portapack-havoc/wiki/Update-firmware) for details how to upgrade.
|
||||
Check the [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Update-firmware) for details how to upgrade.
|
||||
__Warning:__ Since release 1.8.0, some applications has been moved to the SD card as we ran out of flash space.
|
||||
|
||||
### MicroSD card files
|
||||
|
||||
For certain functionality, like the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
|
||||
For certain functionality, like external apps, the world map, GPS simulator, and others you need to uncompress (using [7-zip](https://www.7-zip.org/download.html)) the files from `mayhem_vX.Y.Z_COPY_TO_SDCARD.zip` to a FAT32 formatted MicroSD card.
|
||||
draft: true
|
||||
prerelease: false
|
||||
- name: Upload Firmware TAR Asset
|
||||
id: upload-firmware-tar-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
id: upload-firmware-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@
|
||||
*.map
|
||||
*.lst
|
||||
.dep/
|
||||
build/
|
||||
/build*
|
||||
CMakeFiles/
|
||||
|
||||
# Debugging
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
cache: apt
|
||||
dist: xenial
|
||||
|
||||
env:
|
||||
global:
|
||||
- PROJECT_NAME=PortaPack-HAVOC
|
||||
- SHORT_COMMIT_HASH=`git rev-parse --short HEAD`
|
||||
- VERSION_STRING=nightly-$SHORT_COMMIT_HASH
|
||||
- BUILD_DATE="`date +%Y-%m-%d`"
|
||||
- BUILD_NAME="$PROJECT_NAME-$BUILD_DATE-$SHORT_COMMIT_HASH"
|
||||
- ARTEFACT_BASE=$TRAVIS_BUILD_DIR/artefacts/
|
||||
- ARTEFACT_PATH=$ARTEFACT_BASE/$BUILD_NAME
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa -y
|
||||
- sudo apt-get update -q
|
||||
- sudo apt-get install gcc-arm-embedded -y
|
||||
|
||||
script:
|
||||
# TODO: Introduce top-level Makefile, this is lame.
|
||||
- sed -e "s/\#set(VERSION.*/set(VERSION \"$VERSION_STRING\")/" -i".bak" CMakeLists.txt
|
||||
- mkdir build/
|
||||
- pushd build/
|
||||
- cmake ..
|
||||
- make firmware
|
||||
- popd
|
||||
|
||||
after_success:
|
||||
- mkdir -p $ARTEFACT_PATH
|
||||
# Copy firmware to firmware-bin directory
|
||||
- cd $TRAVIS_BUILD_DIR/build
|
||||
- cp firmware/portapack-h1-havoc.bin $ARTEFACT_PATH/
|
||||
- cp hackrf/firmware/hackrf_usb/hackrf_usb.dfu $ARTEFACT_PATH/
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- cp LICENSE $ARTEFACT_PATH/
|
||||
# Build the archive
|
||||
- cd $ARTEFACT_BASE
|
||||
- tar -cJvf $ARTEFACT_BASE/$BUILD_NAME.tar.xz $BUILD_NAME
|
||||
- md5sum --binary $BUILD_NAME.tar.xz >MD5SUMS
|
||||
- sha256sum --binary $BUILD_NAME.tar.xz >SHA256SUMS
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- coreutils
|
||||
- tar
|
||||
- sed
|
||||
- cmake
|
||||
- dfu-util
|
||||
+10
-2
@@ -18,15 +18,17 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_policy(SET CMP0005 NEW)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||
|
||||
project(portapack-h1)
|
||||
|
||||
set(EXPECTED_GCC_VERSION "9.2.1")
|
||||
|
||||
set(VERSION "$ENV{VERSION_STRING}")
|
||||
if ("$ENV{VERSION_STRING}" STREQUAL "")
|
||||
if ("${VERSION}" STREQUAL "")
|
||||
execute_process(
|
||||
COMMAND git log -n 1 --format=%h
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
@@ -40,6 +42,10 @@ if ("$ENV{VERSION_STRING}" STREQUAL "")
|
||||
else (GIT_VERSION_FOUND)
|
||||
set(VERSION "${GIT_VERSION}")
|
||||
endif (GIT_VERSION_FOUND)
|
||||
|
||||
set(VERSION_NOHASH "dev")
|
||||
else()
|
||||
set(VERSION_NOHASH "${VERSION}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
@@ -48,6 +54,8 @@ execute_process(
|
||||
)
|
||||
set(VERSION_MD5 "0x${VERSION_MD5}")
|
||||
|
||||
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
|
||||
|
||||
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
||||
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
|
||||
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
> [!IMPORTANT]
|
||||
> this repository **has just been moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem](https://github.com/portapack-mayhem/mayhem-firmware). Please keep this in mind to **update your links** accordingly.
|
||||
|
||||
# PortaPack Mayhem
|
||||
|
||||
[](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?
|
||||
|
||||
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
|
||||
|
||||
[](https://www.youtube.com/watch?v=alrFbY5vxt4)
|
||||
[](https://www.youtube.com/watch?v=H-bqdWfbhpg)
|
||||
|
||||
For people familiar with the [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware), this one might be interesting:<br>[What is the HackRF One Portapack H2+?](https://www.youtube.com/watch?v=alrFbY5vxt4)
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/eried/portapack-mayhem/wiki/How-to-collaborate) is always welcomed and appreciated.
|
||||
This repository expands upon the previous work by many people and aims to constantly add new features, bugfixes and generate documentation to make further development easier. [Collaboration](https://github.com/portapack-mayhem/mayhem-firmware/wiki/How-to-collaborate) is always welcomed and appreciated.
|
||||
|
||||
## What to buy?
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), 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.
|
||||
|
||||
@@ -30,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).
|
||||
|
||||
+22
-14
@@ -10,32 +10,40 @@ VOLUME /havoc
|
||||
WORKDIR /havoc/firmware
|
||||
|
||||
# Fetch dependencies from APT
|
||||
RUN apt-get update && \
|
||||
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl && \
|
||||
apt-get -qy autoremove
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
|
||||
&& apt-get -qy autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#Install current pip from PyPa
|
||||
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
||||
python3 get-pip.py
|
||||
|
||||
#Fetch additional dependencies from Python 3.x pip
|
||||
RUN pip install pyyaml
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python && \
|
||||
ln -s /usr/bin/pip3 /usr/bin/pip
|
||||
RUN pip install pyyaml \
|
||||
&& ln -s /usr/bin/python3 /usr/bin/python \
|
||||
&& ln -s /usr/bin/pip3 /usr/bin/pip
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# Grab the GNU ARM toolchain from arm.com
|
||||
# Then extract contents to /opt/build/armbin/
|
||||
RUN mkdir /opt/build && cd /opt/build && \
|
||||
wget -O gcc-arm-none-eabi $ARMBINURL && \
|
||||
mkdir armbin && \
|
||||
tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
RUN mkdir /opt/build \
|
||||
&& cd /opt/build \
|
||||
&& wget -O gcc-arm-none-eabi $ARMBINURL \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin && cd ~/bin && \
|
||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
CMD cd .. && cd build && \
|
||||
cmake .. && make firmware
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
# replace make with ninja temporarily while building your image if you prefer to use that by default
|
||||
CMD ["make"]
|
||||
|
||||
@@ -24,4 +24,4 @@ RUN mkdir ~/bin && cd ~/bin && \
|
||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
||||
|
||||
CMD cd .. && cd build && \
|
||||
cmake .. && make firmware
|
||||
cmake .. && make ppfw
|
||||
|
||||
+22
-14
@@ -10,32 +10,40 @@ VOLUME /havoc
|
||||
WORKDIR /havoc/firmware
|
||||
|
||||
# Fetch dependencies from APT
|
||||
RUN apt-get update && \
|
||||
apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl && \
|
||||
apt-get -qy autoremove
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y git tar wget dfu-util cmake python3 ccache bzip2 liblz4-tool curl ninja-build \
|
||||
&& apt-get -qy autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#Install current pip from PyPa
|
||||
RUN curl https://bootstrap.pypa.io/pip/3.4/get-pip.py -o get-pip.py && \
|
||||
python3 get-pip.py
|
||||
|
||||
#Fetch additional dependencies from Python 3.x pip
|
||||
RUN pip install pyyaml
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python && \
|
||||
ln -s /usr/bin/pip3 /usr/bin/pip
|
||||
RUN pip install pyyaml \
|
||||
&& ln -s /usr/bin/python3 /usr/bin/python \
|
||||
&& ln -s /usr/bin/pip3 /usr/bin/pip
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
# Grab the GNU ARM toolchain from arm.com
|
||||
# Then extract contents to /opt/build/armbin/
|
||||
RUN mkdir /opt/build && cd /opt/build && \
|
||||
wget -O gcc-arm-none-eabi $ARMBINURL && \
|
||||
mkdir armbin && \
|
||||
tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
RUN mkdir /opt/build \
|
||||
&& cd /opt/build \
|
||||
&& wget -O gcc-arm-none-eabi $ARMBINURL \
|
||||
&& mkdir armbin \
|
||||
&& tar --strip=1 -xjvf gcc-arm-none-eabi -C armbin
|
||||
|
||||
# Configure CCACHE
|
||||
RUN mkdir ~/bin && cd ~/bin && \
|
||||
for tool in gcc g++ cpp c++;do ln -s $(which ccache) arm-none-eabi-$tool;done
|
||||
RUN mkdir ~/bin \
|
||||
&& cd ~/bin \
|
||||
&& for tool in gcc g++ cpp c++; do \
|
||||
ln -s $(which ccache) arm-none-eabi-$tool \
|
||||
; done
|
||||
|
||||
CMD cd .. && cd build && \
|
||||
cmake .. && make firmware
|
||||
ADD firmware/tools/docker-entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
||||
# replace make with ninja temporarily while building your image if you prefer to use that by default
|
||||
CMD ["make"]
|
||||
|
||||
+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>
|
||||
|
||||
+50
-1
@@ -18,6 +18,8 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(firmware)
|
||||
|
||||
set(BASEBAND ${PROJECT_SOURCE_DIR}/baseband)
|
||||
@@ -33,6 +35,18 @@ set(LZ4 lz4)
|
||||
|
||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
|
||||
|
||||
# In our current build container cmake need a little help to get the version :)
|
||||
if(NOT DEFINED ${CMAKE_CXX_COMPILER_VERSION})
|
||||
execute_process(COMMAND bash "-c" "arm-none-eabi-g++ -v 2>&1 | grep 'gcc version' | awk '{print $3}'" OUTPUT_VARIABLE CMAKE_CXX_COMPILER_VERSION)
|
||||
string(STRIP ${CMAKE_CXX_COMPILER_VERSION} CMAKE_CXX_COMPILER_VERSION)
|
||||
endif()
|
||||
|
||||
set(GCC_VERSION_MISMATCH 0)
|
||||
if(NOT ${CMAKE_CXX_COMPILER_VERSION} VERSION_EQUAL ${EXPECTED_GCC_VERSION})
|
||||
set(GCC_VERSION_MISMATCH 1)
|
||||
endif()
|
||||
|
||||
add_subdirectory(application)
|
||||
add_subdirectory(baseband)
|
||||
@@ -48,10 +62,19 @@ add_custom_command(
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
firmware ALL
|
||||
firmware
|
||||
DEPENDS ${FIRMWARE_FILENAME} ${HACKRF_FIRMWARE_DFU_FILENAME}
|
||||
)
|
||||
|
||||
if(${GCC_VERSION_MISMATCH})
|
||||
set(COMPILER_MISMATCH_MESSAGE "WARNING: Compiler version mismatch, please use the official compiler version ${EXPECTED_GCC_VERSION} when sharing builds! Current compiler version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
message(${COMPILER_MISMATCH_MESSAGE})
|
||||
add_custom_command(
|
||||
TARGET firmware POST_BUILD
|
||||
COMMAND echo ${COMPILER_MISMATCH_MESSAGE}
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
add_custom_target(
|
||||
program
|
||||
COMMAND ${PROJECT_SOURCE_DIR}/tools/enter_mode.sh hackrf
|
||||
@@ -68,6 +91,21 @@ add_custom_target(
|
||||
DEPENDS program
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${PPFW_FILENAME}
|
||||
|
||||
COMMAND rm -rf firmware_tar
|
||||
COMMAND mkdir -p firmware_tar/FIRMWARE
|
||||
# Using VERSION_NOHASH to avoid dev builds piling up in the FIRMWARE folder of the sd card of testers in #test-drive
|
||||
COMMAND cp ${FIRMWARE_FILENAME} firmware_tar/FIRMWARE/portapack-mayhem_${VERSION_NOHASH}.bin
|
||||
COMMAND mkdir -p firmware_tar/APPS
|
||||
COMMAND cp application/*.ppma firmware_tar/APPS
|
||||
COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
|
||||
DEPENDS firmware ${FIRMWARE_FILENAME}
|
||||
# Dont use VERBATIM here as it prevents usage of globbing (*)
|
||||
# There shouldnt be any funny business in the filenames above :)
|
||||
)
|
||||
|
||||
# TODO: Bad hack to fix location of LICENSE file for tar.
|
||||
add_custom_command(
|
||||
OUTPUT ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
|
||||
@@ -87,7 +125,18 @@ add_custom_command(
|
||||
DEPENDS ${FIRMWARE_NAME}-${VERSION}.tar.bz2 ${FIRMWARE_NAME}-${VERSION}.zip
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
ppfw ALL
|
||||
DEPENDS ${PPFW_FILENAME}
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
oci
|
||||
DEPENDS ${PPFW_FILENAME}
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
release
|
||||
DEPENDS MD5SUMS SHA256SUMS
|
||||
)
|
||||
|
||||
|
||||
@@ -116,13 +116,12 @@ 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
|
||||
${HACKRF_PATH}/firmware/common/usb_request.c
|
||||
${HACKRF_PATH}/firmware/common/usb_standard_request.c
|
||||
${CHIBIOS}/os/various/shell.c
|
||||
${CHIBIOS}/os/various/chprintf.c
|
||||
)
|
||||
|
||||
@@ -130,6 +129,7 @@ set(CSRC
|
||||
# setting.
|
||||
set(CPPSRC
|
||||
main.cpp
|
||||
shell.cpp
|
||||
${COMMON}/acars_packet.cpp
|
||||
${COMMON}/adsb.cpp
|
||||
${COMMON}/adsb_frame.cpp
|
||||
@@ -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
|
||||
@@ -265,7 +267,7 @@ set(CPPSRC
|
||||
apps/ble_tx_app.cpp
|
||||
apps/capture_app.cpp
|
||||
apps/ert_app.cpp
|
||||
apps/gps_sim_app.cpp
|
||||
# apps/gps_sim_app.cpp
|
||||
# apps/lge_app.cpp
|
||||
apps/pocsag_app.cpp
|
||||
# apps/replay_app.cpp
|
||||
@@ -311,9 +313,9 @@ set(CPPSRC
|
||||
apps/ui_settings.cpp
|
||||
apps/ui_siggen.cpp
|
||||
apps/ui_sonde.cpp
|
||||
apps/ui_spectrum_painter_image.cpp
|
||||
apps/ui_spectrum_painter_text.cpp
|
||||
apps/ui_spectrum_painter.cpp
|
||||
# apps/ui_spectrum_painter_image.cpp
|
||||
# apps/ui_spectrum_painter_text.cpp
|
||||
# apps/ui_spectrum_painter.cpp
|
||||
apps/ui_ss_viewer.cpp
|
||||
apps/ui_sstvtx.cpp
|
||||
apps/ui_subghzd.cpp
|
||||
@@ -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}
|
||||
)
|
||||
@@ -424,7 +427,7 @@ set(CPPWARN "-Wall -Wextra -Wno-psabi")
|
||||
# List all default C defines here, like -D_DEBUG=1
|
||||
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
|
||||
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
|
||||
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5}")
|
||||
set(DDEFS "-DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -D'VERSION_STRING=\"${VERSION}\"' -DVERSION_MD5=${VERSION_MD5} -D'GCC_VERSION_MISMATCH=${GCC_VERSION_MISMATCH}'")
|
||||
|
||||
# List all default ASM defines here, like -D_DEBUG=1
|
||||
set(DADEFS)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -293,6 +293,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
||||
ais::format::text(entry_.name),
|
||||
0,
|
||||
GeoPos::alt_unit::METERS,
|
||||
GeoPos::spd_unit::NONE,
|
||||
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
|
||||
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
|
||||
entry_.last_position.true_heading,
|
||||
@@ -315,7 +316,7 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
|
||||
|
||||
void AISRecentEntryDetailView::update_position() {
|
||||
if (send_updates)
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0);
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::focus() {
|
||||
|
||||
@@ -54,24 +54,31 @@ std::string id(ID value) {
|
||||
}
|
||||
|
||||
std::string consumption(Consumption value) {
|
||||
return to_string_dec_uint(value, 10);
|
||||
return to_string_dec_uint(value, 8);
|
||||
}
|
||||
|
||||
std::string commodity_type(CommodityType value) {
|
||||
return to_string_dec_uint(value, 2);
|
||||
}
|
||||
|
||||
std::string tamper_flags(TamperFlags value) {
|
||||
return to_string_hex(value & 0xFFFF, 4); // Note: ignoring bits 32-47 of tamper flags in IDM type due to screen width
|
||||
}
|
||||
|
||||
std::string tamper_flags_scm(TamperFlags value) {
|
||||
return " " + to_string_hex(value & 0x0F, 1) + "/" + to_string_hex(value >> 4, 1); // Physical/Encoder flags
|
||||
}
|
||||
|
||||
} /* namespace format */
|
||||
|
||||
} /* namespace ert */
|
||||
|
||||
void ERTLogger::on_packet(const ert::Packet& packet, const uint32_t target_frequency) {
|
||||
const auto formatted = packet.symbols_formatted();
|
||||
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
|
||||
|
||||
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
|
||||
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors + " ID:" + to_string_dec_uint(packet.id(), 1);
|
||||
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
}
|
||||
|
||||
@@ -81,6 +88,8 @@ void ERTRecentEntry::update(const ert::Packet& packet) {
|
||||
received_count++;
|
||||
|
||||
last_consumption = packet.consumption();
|
||||
last_tamper_flags = packet.tamper_flags();
|
||||
packet_type = packet.type();
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
@@ -91,13 +100,10 @@ void RecentEntriesTable<ERTRecentEntries>::draw(
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
std::string line = ert::format::id(entry.id) + " " + ert::format::commodity_type(entry.commodity_type) + " " + ert::format::consumption(entry.last_consumption);
|
||||
std::string line = ert::format::id(entry.id) + " " + ert::format::commodity_type(entry.commodity_type) + " " + ert::format::consumption(entry.last_consumption) + " ";
|
||||
|
||||
if (entry.received_count > 999) {
|
||||
line += " +++";
|
||||
} else {
|
||||
line += " " + to_string_dec_uint(entry.received_count, 3);
|
||||
}
|
||||
line += (entry.packet_type == ert::Packet::Type::SCM) ? ert::format::tamper_flags_scm(entry.last_tamper_flags) : ert::format::tamper_flags(entry.last_tamper_flags);
|
||||
line += (entry.received_count > 99) ? " ++" : to_string_dec_uint(entry.received_count, 3);
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
|
||||
@@ -72,11 +72,12 @@ struct ERTRecentEntry {
|
||||
|
||||
ert::ID id{ert::invalid_id};
|
||||
ert::CommodityType commodity_type{ert::invalid_commodity_type};
|
||||
ert::Consumption last_consumption{};
|
||||
ert::TamperFlags last_tamper_flags{};
|
||||
ert::Packet::Type packet_type{};
|
||||
|
||||
size_t received_count{0};
|
||||
|
||||
ert::Consumption last_consumption{};
|
||||
|
||||
ERTRecentEntry(
|
||||
const Key& key)
|
||||
: id{key.id},
|
||||
@@ -137,9 +138,10 @@ class ERTAppView : public View {
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"ID", 10},
|
||||
{"Tp", 2},
|
||||
{"Consumpt", 10},
|
||||
{"Cnt", 3},
|
||||
{"Ty", 2},
|
||||
{"Consumpt", 8},
|
||||
{"Tamp", 4},
|
||||
{"Ct", 2},
|
||||
}};
|
||||
ERTRecentEntriesView recent_entries_view{columns, recent};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "string_format.hpp"
|
||||
#include "tonesets.hpp"
|
||||
#include "ui_tone_key.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
using namespace tonekey;
|
||||
using namespace portapack;
|
||||
@@ -40,6 +41,7 @@ void SoundBoardView::stop() {
|
||||
if (is_active())
|
||||
replay_thread.reset();
|
||||
|
||||
audio::output::stop();
|
||||
transmitter_model.disable();
|
||||
tx_view.set_transmitting(false);
|
||||
|
||||
@@ -78,7 +80,13 @@ void SoundBoardView::file_error() {
|
||||
}
|
||||
|
||||
void SoundBoardView::start_tx(const uint32_t id) {
|
||||
if (file_list.empty()) {
|
||||
file_error();
|
||||
return;
|
||||
}
|
||||
|
||||
auto reader = std::make_unique<WAVFileReader>();
|
||||
|
||||
uint32_t tone_key_index = options_tone_key.selected_index();
|
||||
uint32_t sample_rate;
|
||||
|
||||
@@ -108,11 +116,11 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
|
||||
// TODO: Delete all this and use tx model.
|
||||
baseband::set_audiotx_config(
|
||||
1536000 / 20, // Update vu-meter at 20Hz
|
||||
TONES_SAMPLERATE / 20, // Update vu-meter at 20Hz
|
||||
transmitter_model.channel_bandwidth(),
|
||||
0, // Gain is unused
|
||||
8, // shift_bits_s16, default 8 bits, but also unused
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), 1536000),
|
||||
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
|
||||
0, // AM
|
||||
0, // DSB
|
||||
0, // USB
|
||||
@@ -123,6 +131,9 @@ void SoundBoardView::start_tx(const uint32_t id) {
|
||||
transmitter_model.enable();
|
||||
|
||||
tx_view.set_transmitting(true);
|
||||
|
||||
if (tone_key_index == 0)
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
/*void SoundBoardView::show_infos() {
|
||||
@@ -227,6 +238,8 @@ SoundBoardView::SoundBoardView(
|
||||
//&text_title,
|
||||
//&text_duration,
|
||||
//&progressbar,
|
||||
&field_volume,
|
||||
&text_volume_disabled,
|
||||
&page_info,
|
||||
&check_loop,
|
||||
&check_random,
|
||||
@@ -252,6 +265,13 @@ SoundBoardView::SoundBoardView(
|
||||
tone_keys_populate(options_tone_key);
|
||||
options_tone_key.set_selected_index(0);
|
||||
|
||||
text_volume_disabled.hidden(true);
|
||||
options_tone_key.on_change = [this](size_t index, OptionsField::value_t) {
|
||||
bool tone_key_enabled = (index != 0);
|
||||
text_volume_disabled.hidden(!tone_key_enabled);
|
||||
field_volume.hidden(tone_key_enabled);
|
||||
};
|
||||
|
||||
check_loop.set_value(false);
|
||||
check_random.set_value(false);
|
||||
|
||||
@@ -274,7 +294,6 @@ SoundBoardView::SoundBoardView(
|
||||
|
||||
SoundBoardView::~SoundBoardView() {
|
||||
stop();
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -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("");
|
||||
|
||||
@@ -266,6 +266,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
|
||||
get_map_tag(entry_),
|
||||
entry_.pos.altitude,
|
||||
GeoPos::alt_unit::FEET,
|
||||
GeoPos::spd_unit::MPH,
|
||||
entry_.pos.latitude,
|
||||
entry_.pos.longitude,
|
||||
entry_.velo.heading);
|
||||
@@ -290,7 +291,7 @@ void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) {
|
||||
} else if (geomap_view_) {
|
||||
// Map is showing, update the current item.
|
||||
geomap_view_->update_tag(get_map_tag(entry_));
|
||||
geomap_view_->update_position(entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude);
|
||||
geomap_view_->update_position(entry.pos.latitude, entry.pos.longitude, entry.velo.heading, entry.pos.altitude, entry.velo.speed);
|
||||
} else {
|
||||
// Details is showing, update details.
|
||||
refresh_ui();
|
||||
@@ -317,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)
|
||||
@@ -395,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. */
|
||||
|
||||
@@ -85,10 +85,12 @@ ADSBPositionView::ADSBPositionView(
|
||||
nav.push<GeoMapView>(
|
||||
geopos.altitude(),
|
||||
GeoPos::alt_unit::FEET,
|
||||
GeoPos::spd_unit::HIDDEN,
|
||||
geopos.lat(),
|
||||
geopos.lon(),
|
||||
[this](int32_t altitude, float lat, float lon) {
|
||||
[this](int32_t altitude, float lat, float lon, int32_t speed) {
|
||||
geopos.set_altitude(altitude);
|
||||
geopos.set_speed(speed);
|
||||
geopos.set_lat(lat);
|
||||
geopos.set_lon(lon);
|
||||
});
|
||||
|
||||
@@ -58,7 +58,8 @@ class ADSBPositionView : public OptionTabView {
|
||||
private:
|
||||
GeoPos geopos{
|
||||
{0, 2 * 16},
|
||||
GeoPos::FEET};
|
||||
GeoPos::FEET,
|
||||
GeoPos::HIDDEN};
|
||||
|
||||
Button button_set_map{
|
||||
{8 * 8, 6 * 16, 14 * 8, 2 * 16},
|
||||
|
||||
@@ -258,7 +258,7 @@ void APRSTableView::on_pkt(const APRSPacketMessage* message) {
|
||||
std::string source_formatted = packet.get_source_formatted();
|
||||
std::string info_string = packet.get_stream_text();
|
||||
|
||||
rtcGetTime(&RTCD1, &datetime);
|
||||
rtc_time::now(datetime);
|
||||
auto& entry = ::on_packet(recent, packet.get_source());
|
||||
entry.reset_age();
|
||||
entry.inc_hit();
|
||||
@@ -317,7 +317,7 @@ void APRSDetailsView::update() {
|
||||
}
|
||||
|
||||
if (send_updates)
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0);
|
||||
geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude, 0, 0, 0);
|
||||
}
|
||||
|
||||
APRSDetailsView::~APRSDetailsView() {
|
||||
@@ -339,6 +339,7 @@ APRSDetailsView::APRSDetailsView(
|
||||
entry_copy.source_formatted,
|
||||
0, // entry_copy.pos.altitude,
|
||||
GeoPos::alt_unit::FEET,
|
||||
GeoPos::spd_unit::HIDDEN,
|
||||
entry_copy.pos.latitude,
|
||||
entry_copy.pos.longitude,
|
||||
0, /*entry_copy.velo.heading,*/
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -30,6 +30,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{1};
|
||||
|
||||
// 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 = FLASH_STARTING_ADDRESS; i < FLASH_ROM_SIZE / sizeof(read_buffer); i++) {
|
||||
auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer));
|
||||
|
||||
// if file is smaller than 1MB, assume it's a downgrade to an old FW version and ignore the checksum
|
||||
if ((!readResult) || (readResult.value() != sizeof(read_buffer))) {
|
||||
checksum = FLASH_EXPECTED_CHECKSUM;
|
||||
break;
|
||||
}
|
||||
|
||||
checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer));
|
||||
}
|
||||
}
|
||||
return (checksum == FLASH_EXPECTED_CHECKSUM);
|
||||
}
|
||||
|
||||
FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
add_children({&labels,
|
||||
@@ -50,6 +74,17 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
this->firmware_selected(path);
|
||||
}});
|
||||
}
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.tar")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
menu_view.add_item({filename.string().substr(0, max_filename_length),
|
||||
ui::Color::purple(),
|
||||
&bitmap_icon_temperature,
|
||||
[this, path](KeyEvent) {
|
||||
this->firmware_selected(path);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path) {
|
||||
@@ -65,7 +100,46 @@ void FlashUtilityView::firmware_selected(std::filesystem::path::string_type path
|
||||
});
|
||||
}
|
||||
|
||||
bool FlashUtilityView::endsWith(const std::u16string& str, const std::u16string& suffix) {
|
||||
if (str.length() >= suffix.length()) {
|
||||
std::u16string endOfString = str.substr(str.length() - suffix.length());
|
||||
return endOfString == suffix;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::string_type path) {
|
||||
//
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
ui::Color::black());
|
||||
painter.draw_string({12, 24}, this->nav_.style(), "Unpacking TAR file...");
|
||||
|
||||
auto res = UnTar::untar(path, [this](const std::string fileName) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), fileName);
|
||||
});
|
||||
if (res.string().empty()) {
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle({0, 50, portapack::display.width(), 90}, ui::Color::black());
|
||||
painter.draw_string({0, 60}, this->nav_.style(), "BAD TAR FILE");
|
||||
chThdSleepMilliseconds(5000);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
if (endsWith(path, u".tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path).native();
|
||||
}
|
||||
|
||||
if (path.empty() || !valid_firmware_file(path.c_str()))
|
||||
return; // bad firmware image - just returning back to the file list
|
||||
|
||||
ui::Painter painter;
|
||||
painter.fill_rectangle(
|
||||
{0, 0, portapack::display.width(), portapack::display.height()},
|
||||
@@ -74,7 +148,7 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
painter.draw_string({12, 24}, this->nav_.style(), "This will take 15 seconds.");
|
||||
painter.draw_string({12, 64}, this->nav_.style(), "Please wait while LEDs RX");
|
||||
painter.draw_string({12, 84}, this->nav_.style(), "and TX are flashing.");
|
||||
painter.draw_string({12, 124}, this->nav_.style(), "Then restart the device.");
|
||||
painter.draw_string({12, 124}, this->nav_.style(), "Device will then restart.");
|
||||
|
||||
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (path.length() + 1) * 2);
|
||||
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
|
||||
|
||||
@@ -29,11 +29,17 @@
|
||||
#include "ff.h"
|
||||
#include "baseband_api.hpp"
|
||||
#include "core_control.hpp"
|
||||
|
||||
#include "untar.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#define FLASH_ROM_SIZE 1048576
|
||||
#define FLASH_STARTING_ADDRESS 0x00000000
|
||||
#define FLASH_EXPECTED_CHECKSUM 0x00000000
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool valid_firmware_file(std::filesystem::path::string_type path);
|
||||
|
||||
class FlashUtilityView : public View {
|
||||
public:
|
||||
FlashUtilityView(NavigationView& nav);
|
||||
@@ -55,8 +61,10 @@ class FlashUtilityView : public View {
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
|
||||
std::filesystem::path extract_tar(std::filesystem::path::string_type path); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
|
||||
void firmware_selected(std::filesystem::path::string_type path);
|
||||
void flash_firmware(std::filesystem::path::string_type path);
|
||||
bool endsWith(const std::u16string& str, const std::u16string& suffix);
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -78,8 +78,6 @@ void NuoptixView::transmit(bool setup) {
|
||||
}
|
||||
|
||||
transmitter_model.set_rf_amp(true);
|
||||
transmitter_model.set_lna(40);
|
||||
transmitter_model.set_vga(40);
|
||||
transmitter_model.enable();
|
||||
|
||||
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
|
||||
@@ -136,6 +134,7 @@ void NuoptixView::transmit(bool setup) {
|
||||
shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
|
||||
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
|
||||
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
|
||||
|
||||
timecode++;
|
||||
@@ -156,7 +155,7 @@ NuoptixView::NuoptixView(
|
||||
tx_view.on_edit_frequency = [this, &nav]() {
|
||||
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
transmitter_model.target_frequency(f);
|
||||
transmitter_model.set_target_frequency(f);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ void ReconView::set_loop_config(bool v) {
|
||||
persistent_memory::set_recon_continuous(continuous);
|
||||
}
|
||||
|
||||
void ReconView::recon_stop_recording() {
|
||||
void ReconView::recon_stop_recording(bool exiting) {
|
||||
if (is_recording) {
|
||||
if (field_mode.selected_index_value() == SPEC_MODULATION)
|
||||
button_audio_app.set_text("RAW");
|
||||
@@ -113,14 +113,14 @@ void ReconView::recon_stop_recording() {
|
||||
button_config.set_style(&Styles::white);
|
||||
is_recording = false;
|
||||
// repeater mode
|
||||
if (persistent_memory::recon_repeat_recorded()) {
|
||||
if (!exiting && persistent_memory::recon_repeat_recorded()) {
|
||||
start_repeat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReconView::clear_freqlist_for_ui_action() {
|
||||
recon_stop_recording();
|
||||
recon_stop_recording(false);
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
// flag to detect and reload frequency_list
|
||||
@@ -308,9 +308,16 @@ void ReconView::focus() {
|
||||
}
|
||||
|
||||
ReconView::~ReconView() {
|
||||
recon_stop_recording();
|
||||
if (recon_tx) {
|
||||
replay_thread.reset();
|
||||
}
|
||||
|
||||
recon_stop_recording(true);
|
||||
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
transmitter_model.disable();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
@@ -542,7 +549,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_manual_recon.on_select = [this](Button&) {
|
||||
button_remove.set_text("DELETE");
|
||||
button_remove.set_text("<DELETE>");
|
||||
button_add.hidden(false);
|
||||
scanner_mode = false;
|
||||
manual_mode = true;
|
||||
@@ -636,12 +643,12 @@ ReconView::ReconView(NavigationView& nav)
|
||||
scanner_mode = false;
|
||||
button_scanner_mode.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
button_remove.set_text("REMOVE");
|
||||
button_remove.set_text("<REMOVE>");
|
||||
} else {
|
||||
scanner_mode = true;
|
||||
button_scanner_mode.set_style(&Styles::red);
|
||||
button_scanner_mode.set_text("SCAN");
|
||||
button_remove.set_text("DELETE");
|
||||
button_remove.set_text("<DELETE>");
|
||||
}
|
||||
frequency_file_load();
|
||||
if (autostart) {
|
||||
@@ -844,7 +851,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
|
||||
if (status != 1) {
|
||||
status = 1;
|
||||
if (wait != 0) {
|
||||
recon_stop_recording();
|
||||
recon_stop_recording(false);
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
}
|
||||
@@ -1145,7 +1152,7 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
return 0;
|
||||
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
|
||||
recon_stop_recording();
|
||||
recon_stop_recording(false);
|
||||
if (record_view != nullptr) {
|
||||
remove_child(record_view.get());
|
||||
record_view.reset();
|
||||
@@ -1377,10 +1384,31 @@ void ReconView::start_repeat() {
|
||||
repeat_file_error(rawfile, "Can't open file to send to thread");
|
||||
return;
|
||||
}
|
||||
repeat_ready_signal = true;
|
||||
repeat_cur_rep++;
|
||||
// wait for TX if needed (hackish, direct screen update since the UI will be blocked)
|
||||
if (persistent_memory::recon_repeat_delay() > 0) {
|
||||
uint8_t delay = persistent_memory::recon_repeat_delay();
|
||||
Painter p;
|
||||
while (delay > 0) {
|
||||
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
|
||||
|
||||
// update display information
|
||||
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Color::light_grey());
|
||||
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, Styles::red, delay_message);
|
||||
|
||||
// sleep 1 second
|
||||
chThdSleepMilliseconds(1000);
|
||||
|
||||
// decre delay
|
||||
if (delay > 0)
|
||||
delay = delay - 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ReplayThread starts immediately on construction; must be set before creating.
|
||||
repeat_ready_signal = true;
|
||||
repeat_cur_rep++;
|
||||
replay_thread = std::make_unique<ReplayThread>(
|
||||
std::move(reader),
|
||||
/* read_size */ repeat_read_size,
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE u"SETTINGS/recon.cfg"
|
||||
|
||||
enum class recon_mode : uint8_t {
|
||||
Recon,
|
||||
Scanner,
|
||||
@@ -110,7 +108,7 @@ class ReconView : public View {
|
||||
void load_persisted_settings();
|
||||
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
|
||||
// placeholder for possible void recon_start_recording();
|
||||
void recon_stop_recording();
|
||||
void recon_stop_recording(bool exiting);
|
||||
|
||||
// Returns true if 'current_index' is in bounds of frequency_list.
|
||||
bool current_is_valid();
|
||||
|
||||
@@ -106,6 +106,7 @@ void ReconSetupViewMore::save() {
|
||||
persistent_memory::set_recon_repeat_nb(field_repeat_nb.value());
|
||||
persistent_memory::set_recon_repeat_amp(checkbox_repeat_amp.value());
|
||||
persistent_memory::set_recon_repeat_gain(field_repeat_gain.value());
|
||||
persistent_memory::set_recon_repeat_delay(field_repeat_delay.value());
|
||||
};
|
||||
|
||||
void ReconSetupViewMain::focus() {
|
||||
@@ -128,7 +129,9 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
&field_repeat_nb,
|
||||
&checkbox_repeat_amp,
|
||||
&text_repeat_gain,
|
||||
&field_repeat_gain});
|
||||
&field_repeat_gain,
|
||||
&text_repeat_delay,
|
||||
&field_repeat_delay});
|
||||
|
||||
// tx options have to be in yellow to inform the users that activating them will make the device transmit
|
||||
checkbox_repeat_recorded.set_style(&Styles::yellow);
|
||||
@@ -137,6 +140,8 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
checkbox_repeat_amp.set_style(&Styles::yellow);
|
||||
text_repeat_gain.set_style(&Styles::yellow);
|
||||
field_repeat_gain.set_style(&Styles::yellow);
|
||||
text_repeat_delay.set_style(&Styles::yellow);
|
||||
field_repeat_delay.set_style(&Styles::yellow);
|
||||
|
||||
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
|
||||
checkbox_load_repeaters.set_value(persistent_memory::recon_load_repeaters());
|
||||
@@ -148,6 +153,7 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
|
||||
checkbox_repeat_amp.set_value(persistent_memory::recon_repeat_amp());
|
||||
field_repeat_nb.set_value(persistent_memory::recon_repeat_nb());
|
||||
field_repeat_gain.set_value(persistent_memory::recon_repeat_gain());
|
||||
field_repeat_delay.set_value(persistent_memory::recon_repeat_delay());
|
||||
|
||||
// tx warning modal
|
||||
checkbox_repeat_recorded.on_select = [this, &nav](Checkbox&, bool v) {
|
||||
|
||||
@@ -158,7 +158,7 @@ class ReconSetupViewMore : public View {
|
||||
"nb:"};
|
||||
|
||||
NumberField field_repeat_nb{
|
||||
{18 * 8, 165},
|
||||
{17 * 8, 165},
|
||||
2,
|
||||
{1, 99},
|
||||
1,
|
||||
@@ -171,16 +171,28 @@ class ReconSetupViewMore : public View {
|
||||
"AMP,"};
|
||||
|
||||
Text text_repeat_gain{
|
||||
{10 * 8, 196, 5 * 8, 22},
|
||||
{9 * 8, 196, 5 * 8, 22},
|
||||
"GAIN:"};
|
||||
|
||||
NumberField field_repeat_gain{
|
||||
{16 * 8, 196},
|
||||
{14 * 8, 196},
|
||||
2,
|
||||
{0, 47},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
Text text_repeat_delay{
|
||||
{16 * 8, 196, 8 * 8, 22},
|
||||
", delay:"};
|
||||
|
||||
NumberField field_repeat_delay{
|
||||
{24 * 8, 196},
|
||||
3,
|
||||
{0, 254},
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
};
|
||||
|
||||
class ReconSetupView : public View {
|
||||
|
||||
@@ -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 ******************************************/
|
||||
@@ -149,23 +195,10 @@ SetRadioView::SetRadioView(
|
||||
});
|
||||
}
|
||||
|
||||
std::string source_name("---");
|
||||
switch (reference.source) {
|
||||
case ClockManager::ReferenceSource::Xtal:
|
||||
source_name = "HackRF";
|
||||
break;
|
||||
case ClockManager::ReferenceSource::PortaPack:
|
||||
source_name = "PortaPack";
|
||||
break;
|
||||
case ClockManager::ReferenceSource::External:
|
||||
source_name = "External";
|
||||
break;
|
||||
}
|
||||
std::string source_name = clock_manager.get_source();
|
||||
|
||||
value_source.set(source_name);
|
||||
value_source_frequency.set(
|
||||
to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
|
||||
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
|
||||
value_source_frequency.set(clock_manager.get_freq());
|
||||
|
||||
// Make these Text controls look like Labels.
|
||||
label_source.set_style(&Styles::light_grey);
|
||||
@@ -641,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) {
|
||||
@@ -648,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,13 +90,17 @@ SondeView::SondeView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_see_map.on_select = [this, &nav](Button&) {
|
||||
nav.push<GeoMapView>(
|
||||
geomap_view_ = nav.push<GeoMapView>(
|
||||
sonde_id,
|
||||
gps_info.alt,
|
||||
GeoPos::alt_unit::METERS,
|
||||
GeoPos::spd_unit::HIDDEN,
|
||||
gps_info.lat,
|
||||
gps_info.lon,
|
||||
999); // set a dummy heading out of range to draw a cross...probably not ideal?
|
||||
nav.set_on_pop([this]() {
|
||||
geomap_view_ = nullptr;
|
||||
});
|
||||
};
|
||||
|
||||
logger = std::make_unique<SondeLogger>();
|
||||
@@ -123,6 +127,17 @@ SondeView::~SondeView() {
|
||||
audio::output::stop();
|
||||
}
|
||||
|
||||
void SondeView::on_gps(const GPSPosDataMessage* msg) {
|
||||
if (!geomap_view_)
|
||||
return;
|
||||
geomap_view_->update_my_position(msg->lat, msg->lon, msg->altitude);
|
||||
}
|
||||
void SondeView::on_orientation(const OrientationDataMessage* msg) {
|
||||
if (!geomap_view_)
|
||||
return;
|
||||
geomap_view_->update_my_orientation(msg->angle, true);
|
||||
}
|
||||
|
||||
void SondeView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ class SondeView : public View {
|
||||
|
||||
SondeView(NavigationView& nav);
|
||||
~SondeView();
|
||||
SondeView(const SondeView& other) = delete;
|
||||
SondeView& operator=(const SondeView& other) = delete;
|
||||
|
||||
void focus() override;
|
||||
|
||||
@@ -162,7 +164,8 @@ class SondeView : public View {
|
||||
|
||||
GeoPos geopos{
|
||||
{0, 12 * 16},
|
||||
GeoPos::alt_unit::METERS};
|
||||
GeoPos::alt_unit::METERS,
|
||||
GeoPos::spd_unit::HIDDEN};
|
||||
|
||||
Button button_see_qr{
|
||||
{2 * 8, 15 * 16, 12 * 8, 3 * 16},
|
||||
@@ -172,6 +175,8 @@ class SondeView : public View {
|
||||
{16 * 8, 15 * 16, 12 * 8, 3 * 16},
|
||||
"See on map"};
|
||||
|
||||
GeoMapView* geomap_view_{nullptr};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::SondePacket,
|
||||
[this](Message* const p) {
|
||||
@@ -180,6 +185,21 @@ class SondeView : public View {
|
||||
this->on_packet(packet);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_gps{
|
||||
Message::ID::GPSPosData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const GPSPosDataMessage*>(p);
|
||||
this->on_gps(message);
|
||||
}};
|
||||
MessageHandlerRegistration message_handler_orientation{
|
||||
Message::ID::OrientationData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const OrientationDataMessage*>(p);
|
||||
this->on_orientation(message);
|
||||
}};
|
||||
|
||||
void on_gps(const GPSPosDataMessage* msg);
|
||||
void on_orientation(const OrientationDataMessage* msg);
|
||||
void on_packet(const sonde::Packet& packet);
|
||||
char* float_to_char(float x, char* p);
|
||||
};
|
||||
|
||||
@@ -217,6 +217,27 @@ ClockManager::Reference ClockManager::get_reference() const {
|
||||
return reference;
|
||||
}
|
||||
|
||||
std::string ClockManager::get_source() {
|
||||
std::string source_name("---");
|
||||
switch (reference.source) {
|
||||
case ClockManager::ReferenceSource::Xtal:
|
||||
source_name = "HackRF";
|
||||
break;
|
||||
case ClockManager::ReferenceSource::PortaPack:
|
||||
source_name = "PortaPack";
|
||||
break;
|
||||
case ClockManager::ReferenceSource::External:
|
||||
source_name = "External";
|
||||
break;
|
||||
}
|
||||
return source_name;
|
||||
}
|
||||
|
||||
std::string ClockManager::get_freq() {
|
||||
return to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
|
||||
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
|
||||
}
|
||||
|
||||
static void portapack_tcxo_enable() {
|
||||
portapack::io.reference_oscillator(true);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#ifndef __CLOCK_MANAGER_H__
|
||||
#define __CLOCK_MANAGER_H__
|
||||
|
||||
#include "string_format.hpp"
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
@@ -74,6 +75,8 @@ class ClockManager {
|
||||
uint32_t get_frequency_monitor_measurement_in_hertz();
|
||||
|
||||
Reference get_reference() const;
|
||||
std::string get_source();
|
||||
std::string get_freq();
|
||||
|
||||
void enable_clock_output(bool enable);
|
||||
|
||||
|
||||
@@ -27,15 +27,18 @@
|
||||
void config_mode_blink_until_dfu();
|
||||
|
||||
void config_mode_set() {
|
||||
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_GUARD_VALUE);
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_GUARD_VALUE);
|
||||
}
|
||||
|
||||
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_GUARD_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[] = {
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
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
|
||||
@@ -238,6 +270,28 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EventDispatcher::emulateTouch(ui::TouchEvent event) {
|
||||
injected_touch_event = &event;
|
||||
while (injected_touch_event != nullptr) {
|
||||
chThdSleepMilliseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::emulateKeyboard(ui::KeyboardEvent event) {
|
||||
injected_keyboard_event = &event;
|
||||
while (injected_keyboard_event != nullptr) {
|
||||
chThdSleepMilliseconds(5);
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::on_keyboard_event(ui::KeyboardEvent event) {
|
||||
// send the key to focused widget, or parent if not accepts it
|
||||
auto target = context.focus_manager().focus_widget();
|
||||
while ((target != nullptr) && !target->on_keyboard(event)) {
|
||||
target = target->parent();
|
||||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::on_touch_event(ui::TouchEvent event) {
|
||||
/* TODO: Capture widget receiving the Start event, send Move and
|
||||
* End events to the same widget.
|
||||
@@ -258,7 +312,17 @@ void EventDispatcher::on_touch_event(ui::TouchEvent event) {
|
||||
}
|
||||
}
|
||||
|
||||
ui::Widget* EventDispatcher::getTopWidget() {
|
||||
return top_widget;
|
||||
}
|
||||
|
||||
ui::Widget* EventDispatcher::getFocusedWidget() {
|
||||
return context.focus_manager().focus_widget();
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_lcd_frame_sync() {
|
||||
bool waiting_for_frame = this->waiting_for_frame;
|
||||
|
||||
DisplayFrameSyncMessage message;
|
||||
message_map.send(&message);
|
||||
|
||||
@@ -266,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() {
|
||||
@@ -327,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:
|
||||
@@ -86,6 +87,16 @@ class EventDispatcher {
|
||||
events_flag(EVT_MASK_LOCAL);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
private:
|
||||
static Thread* thread_event_loop;
|
||||
|
||||
@@ -98,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);
|
||||
@@ -105,12 +121,16 @@ 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);
|
||||
|
||||
ui::Widget* captured_widget{nullptr};
|
||||
|
||||
void on_touch_event(ui::TouchEvent event);
|
||||
void on_keyboard_event(ui::KeyboardEvent event);
|
||||
|
||||
// void blink_timer();
|
||||
void handle_lcd_frame_sync();
|
||||
|
||||
+18
@@ -44,6 +44,21 @@ set(EXTCPPSRC
|
||||
#lcr
|
||||
external/jammer/main.cpp
|
||||
external/jammer/ui_jammer.cpp
|
||||
|
||||
#gpssim
|
||||
external/gpssim/main.cpp
|
||||
external/gpssim/gps_sim_app.cpp
|
||||
|
||||
#spainter
|
||||
external/spainter/main.cpp
|
||||
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
|
||||
@@ -58,4 +73,7 @@ set(EXTAPPLIST
|
||||
lge
|
||||
lcr
|
||||
jammer
|
||||
gpssim
|
||||
spainter
|
||||
keyfob
|
||||
)
|
||||
|
||||
+23
-2
@@ -28,6 +28,9 @@ MEMORY
|
||||
ram_external_app_lge(rwx) : org = 0xEEF10000, len = 32k
|
||||
ram_external_app_lcr(rwx) : org = 0xEEF20000, len = 32k
|
||||
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
|
||||
@@ -88,18 +91,36 @@ SECTIONS
|
||||
} > ram_external_app_lge
|
||||
|
||||
|
||||
.external_app_lcr : ALIGN(4) SUBALIGN(4)
|
||||
.external_app_lcr : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_lcr.application_information));
|
||||
*(*ui*external_app*lcr*);
|
||||
} > ram_external_app_lcr
|
||||
|
||||
|
||||
.external_app_jammer : ALIGN(4) SUBALIGN(4)
|
||||
.external_app_jammer : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_jammer.application_information));
|
||||
*(*ui*external_app*jammer*);
|
||||
} > ram_external_app_jammer
|
||||
|
||||
.external_app_gpssim : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_gpssim.application_information));
|
||||
*(*ui*external_app*gpssim*);
|
||||
} > ram_external_app_gpssim
|
||||
|
||||
|
||||
.external_app_spainter : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_spainter.application_information));
|
||||
*(*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
|
||||
|
||||
}
|
||||
|
||||
Vendored
+4
-3
@@ -36,7 +36,7 @@
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::gpssim {
|
||||
|
||||
void GpsSimAppView::set_ready() {
|
||||
ready_signal = true;
|
||||
@@ -161,7 +161,8 @@ void GpsSimAppView::handle_replay_thread_done(const uint32_t return_code) {
|
||||
GpsSimAppView::GpsSimAppView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_gps);
|
||||
// baseband::run_image(portapack::spi_flash::image_tag_gps);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({
|
||||
&button_open,
|
||||
@@ -211,4 +212,4 @@ void GpsSimAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
waterfall.set_parent_rect(waterfall_rect);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::gpssim */
|
||||
Vendored
+4
-3
@@ -25,6 +25,7 @@
|
||||
#define __GPS_SIM_APP_HPP__
|
||||
|
||||
#include "app_settings.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
@@ -37,7 +38,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::gpssim {
|
||||
|
||||
class GpsSimAppView : public View {
|
||||
public:
|
||||
@@ -108,7 +109,7 @@ class GpsSimAppView : public View {
|
||||
Checkbox check_loop{
|
||||
{21 * 8, 2 * 16},
|
||||
4,
|
||||
"Loop",
|
||||
LanguageHelper::currentMessages[LANG_LOOP],
|
||||
true};
|
||||
ImageButton button_play{
|
||||
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
|
||||
@@ -142,6 +143,6 @@ class GpsSimAppView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
} /* namespace ui::external_app::gpssim */
|
||||
|
||||
#endif /*__GPS_SIM_APP_HPP__*/
|
||||
+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 "gps_sim_app.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::gpssim {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<GpsSimAppView>();
|
||||
}
|
||||
} // namespace ui::external_app::gpssim
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_gpssim.application_information"), used)) application_information_t _application_information_gpssim = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::gpssim::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "GPSSim",
|
||||
/*.bitmap_data = */ {
|
||||
0xC0,
|
||||
0x07,
|
||||
0xE0,
|
||||
0x0F,
|
||||
0x70,
|
||||
0x1F,
|
||||
0x78,
|
||||
0x3E,
|
||||
0x78,
|
||||
0x3C,
|
||||
0x78,
|
||||
0x38,
|
||||
0x78,
|
||||
0x30,
|
||||
0x78,
|
||||
0x38,
|
||||
0x78,
|
||||
0x3C,
|
||||
0x70,
|
||||
0x1E,
|
||||
0x70,
|
||||
0x1F,
|
||||
0xE0,
|
||||
0x0F,
|
||||
0xC0,
|
||||
0x07,
|
||||
0x80,
|
||||
0x03,
|
||||
0x20,
|
||||
0x09,
|
||||
0x50,
|
||||
0x14,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_gpssim */ {'P', 'G', 'P', 'S'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+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 */
|
||||
+21
-7
@@ -205,11 +205,13 @@ class Sprite {
|
||||
byte who;
|
||||
byte _speed;
|
||||
byte dir;
|
||||
byte lastDir;
|
||||
byte phase;
|
||||
|
||||
// Sprite bits
|
||||
byte palette2; // 4->16 color map index
|
||||
byte bits; // index of sprite bits
|
||||
|
||||
signed char sy;
|
||||
|
||||
void Init(const byte* s) {
|
||||
@@ -274,9 +276,17 @@ class Sprite {
|
||||
f = pgm_read_byte(_pacLeftAnim + f);
|
||||
else if (dir == MRight)
|
||||
f = pgm_read_byte(_pacRightAnim + f);
|
||||
else
|
||||
else if (dir == MDown || dir == MUp)
|
||||
f = pgm_read_byte(_pacVAnim + f);
|
||||
if (dir == MUp)
|
||||
else if (dir == MStopped) {
|
||||
if (lastDir == MLeft)
|
||||
f = pgm_read_byte(_pacLeftAnim + f);
|
||||
else if (lastDir == MRight)
|
||||
f = pgm_read_byte(_pacRightAnim + f);
|
||||
else if (lastDir == MDown || lastDir == MUp)
|
||||
f = pgm_read_byte(_pacVAnim + f);
|
||||
}
|
||||
if ((dir == MUp) || (dir == MStopped && lastDir == MUp))
|
||||
sy = -1;
|
||||
bits = f + PACMANSPRITE;
|
||||
}
|
||||
@@ -838,15 +848,19 @@ class Playfield {
|
||||
else if (DEMO == 0 && s->who == PACMAN && choice[3] < 0x7FFF && but_RIGHT)
|
||||
dir = MRight;
|
||||
|
||||
else if (DEMO == 0 && choice[0] < 0x7FFF && s->who == PACMAN && dir == MUp)
|
||||
else if (DEMO == 0 && choice[0] < 0x7FFF && s->who == PACMAN && dir == MUp) {
|
||||
dir = MUp;
|
||||
else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft)
|
||||
s->lastDir = MUp;
|
||||
} else if (DEMO == 0 && choice[1] < 0x7FFF && s->who == PACMAN && dir == MLeft) {
|
||||
dir = MLeft;
|
||||
else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown)
|
||||
s->lastDir = MLeft;
|
||||
} else if (DEMO == 0 && choice[2] < 0x7FFF && s->who == PACMAN && dir == MDown) {
|
||||
dir = MDown;
|
||||
else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight)
|
||||
s->lastDir = MDown;
|
||||
} else if (DEMO == 0 && choice[3] < 0x7FFF && s->who == PACMAN && dir == MRight) {
|
||||
dir = MRight;
|
||||
else if ((DEMO == 0 && s->who != PACMAN) || DEMO == 1) {
|
||||
s->lastDir = MRight;
|
||||
} else if ((DEMO == 0 && s->who != PACMAN) || DEMO == 1) {
|
||||
// Don't choose opposite of current direction?
|
||||
|
||||
int16_t dist = choice[4 - dir]; // favor current direction
|
||||
|
||||
+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_spectrum_painter.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::spainter {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<SpectrumPainterView>();
|
||||
}
|
||||
} // namespace ui::external_app::spainter
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_spainter.application_information"), used)) application_information_t _application_information_spainter = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::spainter::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "S.Painter",
|
||||
/*.bitmap_data = */ {
|
||||
0xFE,
|
||||
0x3F,
|
||||
0xFF,
|
||||
0x3F,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xBF,
|
||||
0xFE,
|
||||
0xBF,
|
||||
0x00,
|
||||
0x80,
|
||||
0x80,
|
||||
0xFF,
|
||||
0x80,
|
||||
0x00,
|
||||
0x80,
|
||||
0x00,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
0xC0,
|
||||
0x01,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_spainter */ {'P', 'S', 'P', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+4
-3
@@ -31,12 +31,13 @@
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::spainter {
|
||||
|
||||
SpectrumPainterView::SpectrumPainterView(
|
||||
NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
baseband::run_image(spi_flash::image_tag_spectrum_painter);
|
||||
// baseband::run_image(spi_flash::image_tag_spectrum_painter);
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({
|
||||
&labels,
|
||||
@@ -198,4 +199,4 @@ void SpectrumPainterView::paint(Painter& painter) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::spainter
|
||||
+4
-3
@@ -22,6 +22,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_language.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
@@ -37,7 +38,7 @@
|
||||
#include "ui_spectrum_painter_image.hpp"
|
||||
#include "ui_spectrum_painter_text.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::spainter {
|
||||
|
||||
class SpectrumPainterView : public View {
|
||||
public:
|
||||
@@ -115,7 +116,7 @@ class SpectrumPainterView : public View {
|
||||
Checkbox check_loop{
|
||||
{21 * 8, footer_location + 1 * 16},
|
||||
4,
|
||||
"Loop",
|
||||
LanguageHelper::currentMessages[LANG_LOOP],
|
||||
true};
|
||||
|
||||
ImageButton button_play{
|
||||
@@ -163,4 +164,4 @@ class SpectrumPainterView : public View {
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::spainter
|
||||
+2
-2
@@ -29,7 +29,7 @@
|
||||
#include "file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::spainter {
|
||||
|
||||
SpectrumInputImageView::SpectrumInputImageView(NavigationView& nav) {
|
||||
hidden(true);
|
||||
@@ -254,4 +254,4 @@ void SpectrumInputImageView::paint(Painter& painter) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::spainter
|
||||
+2
-2
@@ -32,7 +32,7 @@
|
||||
#include "portapack.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::spainter {
|
||||
|
||||
class SpectrumInputImageView : public View {
|
||||
public:
|
||||
@@ -63,4 +63,4 @@ class SpectrumInputImageView : public View {
|
||||
bool drawBMP_scaled(const ui::Rect r, const std::string file);
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::spainter
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
#include "file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::spainter {
|
||||
|
||||
SpectrumInputTextView::SpectrumInputTextView(NavigationView& nav) {
|
||||
hidden(true);
|
||||
@@ -105,4 +105,4 @@ std::vector<uint8_t> SpectrumInputTextView::get_line(uint16_t y) {
|
||||
|
||||
return data;
|
||||
}
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::spainter
|
||||
+2
-2
@@ -32,7 +32,7 @@
|
||||
#include "portapack.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace ui::external_app::spainter {
|
||||
|
||||
class SpectrumInputTextView : public View {
|
||||
public:
|
||||
@@ -109,4 +109,4 @@ class SpectrumInputTextView : public View {
|
||||
"Set message"};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace ui::external_app::spainter
|
||||
@@ -171,12 +171,13 @@ static bool encoder_update(const uint8_t raw) {
|
||||
}
|
||||
|
||||
static bool encoder_read() {
|
||||
const auto delta = encoder.update(
|
||||
encoder_debounce[0].state() | (injected_encoder == 1),
|
||||
encoder_debounce[1].state() | (injected_encoder == 2));
|
||||
auto delta = encoder.update(encoder_debounce[0].state(), encoder_debounce[1].state());
|
||||
|
||||
if (injected_encoder > 0)
|
||||
if (injected_encoder > 0) {
|
||||
if (injected_encoder == 1) delta = -1;
|
||||
if (injected_encoder == 2) delta = 1;
|
||||
injected_encoder = 0;
|
||||
}
|
||||
|
||||
if (delta != 0) {
|
||||
encoder_position += delta;
|
||||
|
||||
@@ -157,7 +157,7 @@ static void event_loop() {
|
||||
[&event_dispatcher](const Message* const) {
|
||||
event_dispatcher.set_display_sleep(true);
|
||||
}};
|
||||
|
||||
portapack::setEventDispatcherToUSBSerial(&event_dispatcher);
|
||||
event_dispatcher.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -542,4 +543,8 @@ void shutdown(const bool leave_screen_on) {
|
||||
shutdown_base();
|
||||
}
|
||||
|
||||
void setEventDispatcherToUSBSerial(EventDispatcher* evt) {
|
||||
usb_serial.setEventDispatcher(evt);
|
||||
}
|
||||
|
||||
} /* namespace portapack */
|
||||
|
||||
@@ -68,6 +68,8 @@ bool get_antenna_bias();
|
||||
bool init();
|
||||
void shutdown(const bool leave_screen_on = false);
|
||||
|
||||
void setEventDispatcherToUSBSerial(EventDispatcher* evt);
|
||||
|
||||
Backlight* backlight();
|
||||
|
||||
} /* namespace portapack */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file shell.c
|
||||
* @brief Simple CLI shell code.
|
||||
*
|
||||
* @addtogroup SHELL
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
#include "shell.hpp"
|
||||
#include "chprintf.h"
|
||||
#include "portapack.hpp"
|
||||
|
||||
/**
|
||||
* @brief Shell termination event source.
|
||||
*/
|
||||
EventSource shell_terminated;
|
||||
|
||||
static char* _strtok(char* str, const char* delim, char** saveptr) {
|
||||
char* token;
|
||||
if (str)
|
||||
*saveptr = str;
|
||||
token = *saveptr;
|
||||
|
||||
if (!token)
|
||||
return NULL;
|
||||
|
||||
token += strspn(token, delim);
|
||||
*saveptr = strpbrk(token, delim);
|
||||
if (*saveptr)
|
||||
*(*saveptr)++ = '\0';
|
||||
|
||||
return *token ? token : NULL;
|
||||
}
|
||||
|
||||
static void usage(BaseSequentialStream* chp, char* p) {
|
||||
chprintf(chp, "Usage: %s\r\n", p);
|
||||
}
|
||||
|
||||
static void list_commands(BaseSequentialStream* chp, const ShellCommand* scp) {
|
||||
while (scp->sc_name != NULL) {
|
||||
chprintf(chp, "%s ", scp->sc_name);
|
||||
scp++;
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_info(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
usage(chp, const_cast<char*>("info"));
|
||||
return;
|
||||
}
|
||||
|
||||
chprintf(chp, "Kernel: %s\r\n", CH_KERNEL_VERSION);
|
||||
#ifdef CH_COMPILER_NAME
|
||||
chprintf(chp, "Compiler: %s\r\n", CH_COMPILER_NAME);
|
||||
#endif
|
||||
chprintf(chp, "Architecture: %s\r\n", CH_ARCHITECTURE_NAME);
|
||||
#ifdef CH_CORE_VARIANT_NAME
|
||||
chprintf(chp, "Core Variant: %s\r\n", CH_CORE_VARIANT_NAME);
|
||||
#endif
|
||||
#ifdef CH_PORT_INFO
|
||||
chprintf(chp, "Port Info: %s\r\n", CH_PORT_INFO);
|
||||
#endif
|
||||
#ifdef PLATFORM_NAME
|
||||
chprintf(chp, "Platform: %s\r\n", PLATFORM_NAME);
|
||||
#endif
|
||||
#ifdef BOARD_NAME
|
||||
chprintf(chp, "Board: %s\r\n", BOARD_NAME);
|
||||
#endif
|
||||
#ifdef VERSION_STRING
|
||||
chprintf(chp, "Mayhem Version: %s\r\n", VERSION_STRING);
|
||||
#endif
|
||||
chprintf(chp, "HackRF Board Rev: %s\r\n", hackrf_r9 ? "R9" : "R1-R8");
|
||||
chprintf(chp, "Reference Source: %s\r\n", portapack::clock_manager.get_source().c_str());
|
||||
chprintf(chp, "Reference Freq: %s\r\n", portapack::clock_manager.get_freq().c_str());
|
||||
#ifdef __DATE__
|
||||
#ifdef __TIME__
|
||||
chprintf(chp, "Build time: %s%s%s\r\n", __DATE__, " - ", __TIME__);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cmd_systime(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
usage(chp, const_cast<char*>("systime"));
|
||||
return;
|
||||
}
|
||||
chprintf(chp, "%lu\r\n", (unsigned long)chTimeNow());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Array of the default commands.
|
||||
*/
|
||||
static ShellCommand local_commands[] = {
|
||||
{"info", cmd_info},
|
||||
{"systime", cmd_systime},
|
||||
{NULL, NULL}};
|
||||
|
||||
static bool_t cmdexec(const ShellCommand* scp, BaseSequentialStream* chp, char* name, int argc, char* argv[]) {
|
||||
while (scp->sc_name != NULL) {
|
||||
if (strcmp(scp->sc_name, name) == 0) {
|
||||
scp->sc_function(chp, argc, argv);
|
||||
return FALSE;
|
||||
}
|
||||
scp++;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shell thread function.
|
||||
*
|
||||
* @param[in] p pointer to a @p BaseSequentialStream object
|
||||
* @return Termination reason.
|
||||
* @retval RDY_OK terminated by command.
|
||||
* @retval RDY_RESET terminated by reset condition on the I/O channel.
|
||||
*
|
||||
* @notapi
|
||||
*/
|
||||
static msg_t shell_thread(void* p) {
|
||||
int n;
|
||||
BaseSequentialStream* chp = ((ShellConfig*)p)->sc_channel;
|
||||
const ShellCommand* scp = ((ShellConfig*)p)->sc_commands;
|
||||
char *lp, *cmd, *tokp, line[SHELL_MAX_LINE_LENGTH];
|
||||
char* args[SHELL_MAX_ARGUMENTS + 1];
|
||||
|
||||
chRegSetThreadName("shell");
|
||||
chprintf(chp, "\r\nChibiOS/RT Shell\r\n");
|
||||
while (TRUE) {
|
||||
chprintf(chp, "ch> ");
|
||||
if (shellGetLine(chp, line, sizeof(line))) {
|
||||
chprintf(chp, "\r\nlogout");
|
||||
break;
|
||||
}
|
||||
lp = _strtok(line, " \t", &tokp);
|
||||
cmd = lp;
|
||||
n = 0;
|
||||
while ((lp = _strtok(NULL, " \t", &tokp)) != NULL) {
|
||||
if (n >= SHELL_MAX_ARGUMENTS) {
|
||||
chprintf(chp, "too many arguments\r\n");
|
||||
cmd = NULL;
|
||||
break;
|
||||
}
|
||||
args[n++] = lp;
|
||||
}
|
||||
args[n] = NULL;
|
||||
if (cmd != NULL) {
|
||||
if (strcmp(cmd, "exit") == 0) {
|
||||
if (n > 0) {
|
||||
usage(chp, const_cast<char*>("exit"));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} else if (strcmp(cmd, "help") == 0) {
|
||||
if (n > 0) {
|
||||
usage(chp, const_cast<char*>("help"));
|
||||
continue;
|
||||
}
|
||||
chprintf(chp, "Commands: help exit ");
|
||||
list_commands(chp, local_commands);
|
||||
if (scp != NULL)
|
||||
list_commands(chp, scp);
|
||||
chprintf(chp, "\r\n");
|
||||
} else if (cmdexec(local_commands, chp, cmd, n, args) &&
|
||||
((scp == NULL) || cmdexec(scp, chp, cmd, n, args))) {
|
||||
chprintf(chp, "%s", cmd);
|
||||
chprintf(chp, " ?\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
shellExit(RDY_OK);
|
||||
/* Never executed, silencing a warning.*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shell manager initialization.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void shellInit(void) {
|
||||
chEvtInit(&shell_terminated);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Terminates the shell.
|
||||
* @note Must be invoked from the command handlers.
|
||||
* @note Does not return.
|
||||
*
|
||||
* @param[in] msg shell exit code
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
void shellExit(msg_t msg) {
|
||||
/* Atomically broadcasting the event source and terminating the thread,
|
||||
there is not a chSysUnlock() because the thread terminates upon return.*/
|
||||
chSysLock();
|
||||
chEvtBroadcastI(&shell_terminated);
|
||||
chThdExitS(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Spawns a new shell.
|
||||
* @pre @p CH_USE_HEAP and @p CH_USE_DYNAMIC must be enabled.
|
||||
*
|
||||
* @param[in] scp pointer to a @p ShellConfig object
|
||||
* @param[in] size size of the shell working area to be allocated
|
||||
* @param[in] prio priority level for the new shell
|
||||
* @return A pointer to the shell thread.
|
||||
* @retval NULL thread creation failed because memory allocation.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
#if CH_USE_HEAP && CH_USE_DYNAMIC
|
||||
Thread* shellCreate(const ShellConfig* scp, size_t size, tprio_t prio) {
|
||||
return chThdCreateFromHeap(NULL, size, prio, shell_thread, (void*)scp);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create statically allocated shell thread.
|
||||
*
|
||||
* @param[in] scp pointer to a @p ShellConfig object
|
||||
* @param[in] wsp pointer to a working area dedicated to the shell thread stack
|
||||
* @param[in] size size of the shell working area
|
||||
* @param[in] prio priority level for the new shell
|
||||
* @return A pointer to the shell thread.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
Thread* shellCreateStatic(const ShellConfig* scp, void* wsp, size_t size, tprio_t prio) {
|
||||
return chThdCreateStatic(wsp, size, prio, shell_thread, (void*)scp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a whole line from the input channel.
|
||||
*
|
||||
* @param[in] chp pointer to a @p BaseSequentialStream object
|
||||
* @param[in] line pointer to the line buffer
|
||||
* @param[in] size buffer maximum length
|
||||
* @return The operation status.
|
||||
* @retval TRUE the channel was reset or CTRL-D pressed.
|
||||
* @retval FALSE operation successful.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
bool_t shellGetLine(BaseSequentialStream* chp, char* line, unsigned size) {
|
||||
char* p = line;
|
||||
|
||||
while (TRUE) {
|
||||
char c;
|
||||
|
||||
if (chSequentialStreamRead(chp, (uint8_t*)&c, 1) == 0)
|
||||
return TRUE;
|
||||
if (c == 4) {
|
||||
chprintf(chp, "^D");
|
||||
return TRUE;
|
||||
}
|
||||
if ((c == 8) || (c == 127)) {
|
||||
if (p != line) {
|
||||
chSequentialStreamPut(chp, c);
|
||||
chSequentialStreamPut(chp, 0x20);
|
||||
chSequentialStreamPut(chp, c);
|
||||
p--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (c == '\r') {
|
||||
chprintf(chp, "\r\n");
|
||||
*p = 0;
|
||||
return FALSE;
|
||||
}
|
||||
if (c < 0x20)
|
||||
continue;
|
||||
if (p < line + size - 1) {
|
||||
chSequentialStreamPut(chp, c);
|
||||
*p++ = (char)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file shell.h
|
||||
* @brief Simple CLI shell header.
|
||||
*
|
||||
* @addtogroup SHELL
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef _SHELL_H_
|
||||
#define _SHELL_H_
|
||||
|
||||
/**
|
||||
* @brief Shell maximum input line length.
|
||||
*/
|
||||
#if !defined(SHELL_MAX_LINE_LENGTH) || defined(__DOXYGEN__)
|
||||
#define SHELL_MAX_LINE_LENGTH 64
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Shell maximum arguments per command.
|
||||
*/
|
||||
#if !defined(SHELL_MAX_ARGUMENTS) || defined(__DOXYGEN__)
|
||||
#define SHELL_MAX_ARGUMENTS 6
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Command handler function type.
|
||||
*/
|
||||
typedef void (*shellcmd_t)(BaseSequentialStream* chp, int argc, char* argv[]);
|
||||
|
||||
/**
|
||||
* @brief Custom command entry type.
|
||||
*/
|
||||
typedef struct {
|
||||
const char* sc_name; /**< @brief Command name. */
|
||||
shellcmd_t sc_function; /**< @brief Command function. */
|
||||
} ShellCommand;
|
||||
|
||||
/**
|
||||
* @brief Shell descriptor type.
|
||||
*/
|
||||
typedef struct {
|
||||
BaseSequentialStream* sc_channel; /**< @brief I/O channel associated
|
||||
to the shell. */
|
||||
const ShellCommand* sc_commands; /**< @brief Shell extra commands
|
||||
table. */
|
||||
} ShellConfig;
|
||||
|
||||
#if !defined(__DOXYGEN__)
|
||||
extern EventSource shell_terminated;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void shellInit(void);
|
||||
void shellExit(msg_t msg);
|
||||
Thread* shellCreate(const ShellConfig* scp, size_t size, tprio_t prio);
|
||||
Thread* shellCreateStatic(const ShellConfig* scp, void* wsp, size_t size, tprio_t prio);
|
||||
bool_t shellGetLine(BaseSequentialStream* chp, char* line, unsigned size);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _SHELL_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) {
|
||||
|
||||
@@ -92,6 +92,28 @@ void FreqManUIList::on_blur() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool FreqManUIList::on_keyboard(const KeyboardEvent key) {
|
||||
if (!db_ || db_->empty())
|
||||
return false;
|
||||
|
||||
auto delta = 0;
|
||||
if (key == '-' && get_index() > 0) delta = -1;
|
||||
if (key == '+' && get_index() < db_->entry_count() - 1) delta = 1;
|
||||
if (delta != 0) {
|
||||
adjust_selected_index(delta);
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
if (key == 10) {
|
||||
if (on_select) {
|
||||
on_select(get_index());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FreqManUIList::on_key(const KeyEvent key) {
|
||||
if (!db_ || db_->empty())
|
||||
return false;
|
||||
|
||||
@@ -50,6 +50,8 @@ class FreqManUIList : public Widget {
|
||||
void on_blur() override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
|
||||
void set_parent_rect(Rect new_parent_rect) override;
|
||||
|
||||
void set_index(size_t index);
|
||||
|
||||
@@ -38,13 +38,17 @@ namespace ui {
|
||||
|
||||
GeoPos::GeoPos(
|
||||
const Point pos,
|
||||
const alt_unit altitude_unit)
|
||||
: altitude_unit_(altitude_unit) {
|
||||
const alt_unit altitude_unit,
|
||||
const spd_unit speed_unit)
|
||||
: altitude_unit_(altitude_unit), speed_unit_(speed_unit) {
|
||||
set_parent_rect({pos, {30 * 8, 3 * 16}});
|
||||
|
||||
add_children({&labels_position,
|
||||
&label_spd_position,
|
||||
&field_altitude,
|
||||
&field_speed,
|
||||
&text_alt_unit,
|
||||
&text_speed_unit,
|
||||
&field_lat_degrees,
|
||||
&field_lat_minutes,
|
||||
&field_lat_seconds,
|
||||
@@ -56,10 +60,12 @@ GeoPos::GeoPos(
|
||||
|
||||
// Defaults
|
||||
set_altitude(0);
|
||||
set_speed(0);
|
||||
set_lat(0);
|
||||
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();
|
||||
|
||||
@@ -67,10 +73,11 @@ GeoPos::GeoPos(
|
||||
text_lon_decimal.set(to_string_decimal(lon_value, 5));
|
||||
|
||||
if (on_change && report_change)
|
||||
on_change(altitude(), lat_value, lon_value);
|
||||
on_change(altitude(), lat_value, lon_value, speed());
|
||||
};
|
||||
|
||||
field_altitude.on_change = changed_fn;
|
||||
field_speed.on_change = changed_fn;
|
||||
field_lat_degrees.on_change = changed_fn;
|
||||
field_lat_minutes.on_change = changed_fn;
|
||||
field_lat_seconds.on_change = changed_fn;
|
||||
@@ -78,12 +85,41 @@ 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");
|
||||
if (speed_unit_ == HIDDEN) {
|
||||
text_speed_unit.hidden(true);
|
||||
label_spd_position.hidden(true);
|
||||
field_speed.hidden(true);
|
||||
}
|
||||
}
|
||||
|
||||
void GeoPos::set_read_only(bool v) {
|
||||
// only setting altitude to read-only (allow manual panning via lat/lon fields)
|
||||
field_altitude.set_focusable(!v);
|
||||
field_speed.set_focusable(!v);
|
||||
}
|
||||
|
||||
// Stupid hack to avoid an event loop
|
||||
@@ -98,14 +134,18 @@ void GeoPos::focus() {
|
||||
field_lat_degrees.focus();
|
||||
}
|
||||
|
||||
void GeoPos::hide_altitude() {
|
||||
void GeoPos::hide_altandspeed() {
|
||||
// Color altitude grey to indicate it's not updated in manual panning mode
|
||||
field_altitude.set_style(&Styles::grey);
|
||||
field_speed.set_style(&Styles::grey);
|
||||
}
|
||||
|
||||
void GeoPos::set_altitude(int32_t altitude) {
|
||||
field_altitude.set_value(altitude);
|
||||
}
|
||||
void GeoPos::set_speed(int32_t speed) {
|
||||
field_speed.set_value(speed);
|
||||
}
|
||||
|
||||
void GeoPos::set_lat(float lat) {
|
||||
field_lat_degrees.set_value(lat);
|
||||
@@ -139,122 +179,237 @@ int32_t GeoPos::altitude() {
|
||||
return field_altitude.value();
|
||||
};
|
||||
|
||||
int32_t GeoPos::speed() {
|
||||
return field_speed.value();
|
||||
};
|
||||
|
||||
GeoMap::GeoMap(
|
||||
Rect parent_rect)
|
||||
: Widget{parent_rect}, markerListLen(0) {
|
||||
}
|
||||
|
||||
bool GeoMap::on_encoder(const EncoderEvent delta) {
|
||||
if ((delta > 0) && (map_zoom < 5)) {
|
||||
map_zoom++;
|
||||
|
||||
// Ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
|
||||
if (geomap_rect_width % map_zoom != 0) {
|
||||
map_zoom--;
|
||||
// Valid map_zoom values are -2 to -MAX_MAP_ZOOM_OUT, and +1 to +MAX_MAP_ZOOM_IN (values of 0 and -1 are not permitted)
|
||||
if (delta > 0) {
|
||||
if (map_zoom < MAX_MAP_ZOOM_IN) {
|
||||
if (map_zoom == -2) {
|
||||
map_zoom = 1;
|
||||
} else {
|
||||
// zoom in faster after exceeding the map resolution limit
|
||||
map_zoom += (map_zoom >= MAP_ZOOM_RESOLUTION_LIMIT) ? map_zoom : 1;
|
||||
}
|
||||
}
|
||||
} else if (delta < 0) {
|
||||
if (map_zoom > -MAX_MAP_ZOOM_OUT) {
|
||||
if (map_zoom == 1) {
|
||||
map_zoom = -2;
|
||||
} else {
|
||||
if (map_zoom > MAP_ZOOM_RESOLUTION_LIMIT)
|
||||
map_zoom /= 2;
|
||||
else
|
||||
map_zoom--;
|
||||
}
|
||||
}
|
||||
} else if ((delta < 0) && (map_zoom > 1)) {
|
||||
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;
|
||||
}
|
||||
|
||||
void GeoMap::map_zoom_line(ui::Color* buffer) {
|
||||
if (map_zoom <= 1) {
|
||||
return;
|
||||
}
|
||||
void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
|
||||
if (map_zoom == 1) {
|
||||
map_file.read(buffer, pixels << 1);
|
||||
} else if (map_zoom > 1) {
|
||||
map_file.read(buffer, (pixels / map_zoom) << 1);
|
||||
|
||||
// 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 (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];
|
||||
// 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.
|
||||
// (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];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui::Color* zoom_out_buffer = new ui::Color[(pixels * (-map_zoom))];
|
||||
map_file.read(zoom_out_buffer, (pixels * (-map_zoom)) << 1);
|
||||
|
||||
// Zoom out: Collapse each group of "-map_zoom" pixels into one pixel.
|
||||
// Future TODO: Average each group of pixels (in both X & Y directions if possible).
|
||||
for (int i = 0; i < geomap_rect_width; i++) {
|
||||
buffer[i] = zoom_out_buffer[i * (-map_zoom)];
|
||||
}
|
||||
delete zoom_out_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
uint16_t line, j;
|
||||
uint32_t zoom_seek_x, zoom_seek_y;
|
||||
std::array<ui::Color, 240> map_line_buffer;
|
||||
const auto r = screen_rect();
|
||||
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);
|
||||
if (markerListUpdated || (x_diff >= map_zoom * 3) || (y_diff >= map_zoom * 3)) {
|
||||
if (map_zoom == 1) {
|
||||
zoom_seek_x = zoom_seek_y = 0;
|
||||
} else {
|
||||
zoom_seek_x = (r.width() - (r.width() / map_zoom)) / 2;
|
||||
zoom_seek_y = (r.height() - (r.height() / map_zoom)) / 2;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
for (line = 0; line < (r.height() / map_zoom); line++) {
|
||||
map_file.seek(4 + ((x_pos + zoom_seek_x + (map_width * (y_pos + line + zoom_seek_y))) << 1));
|
||||
map_file.read(map_line_buffer.data(), (r.width() / map_zoom) << 1);
|
||||
map_zoom_line(map_line_buffer.data());
|
||||
for (j = 0; j < map_zoom; j++) {
|
||||
display.draw_pixels({0, r.top() + (line * map_zoom) + 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());
|
||||
}
|
||||
}
|
||||
|
||||
bool GeoMap::on_keyboard(KeyboardEvent key) {
|
||||
if (key == '+' || key == ' ') return on_encoder(1);
|
||||
if (key == '-') return on_encoder(-1);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GeoMap::on_touch(const TouchEvent event) {
|
||||
if ((event.type == TouchEvent::Type::Start) && (mode_ == PROMPT)) {
|
||||
set_highlighted(true);
|
||||
@@ -268,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;
|
||||
|
||||
@@ -307,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) {
|
||||
@@ -323,21 +484,35 @@ bool GeoMap::manual_panning() {
|
||||
}
|
||||
|
||||
void GeoMap::draw_scale(Painter& painter) {
|
||||
uint16_t km = 100;
|
||||
uint16_t scale_width = 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) {
|
||||
@@ -354,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
|
||||
@@ -374,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;
|
||||
@@ -406,14 +592,37 @@ 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_position(float lat, float lon, uint16_t angle, int32_t altitude) {
|
||||
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();
|
||||
return;
|
||||
@@ -422,12 +631,14 @@ void GeoMapView::update_position(float lat, float lon, uint16_t angle, int32_t a
|
||||
lat_ = lat;
|
||||
lon_ = lon;
|
||||
altitude_ = altitude;
|
||||
speed_ = speed;
|
||||
|
||||
// Stupid hack to avoid an event loop
|
||||
geopos.set_report_change(false);
|
||||
geopos.set_lat(lat_);
|
||||
geopos.set_lon(lon_);
|
||||
geopos.set_altitude(altitude_);
|
||||
geopos.set_speed(speed_);
|
||||
geopos.set_report_change(true);
|
||||
|
||||
geomap.set_angle(angle);
|
||||
@@ -446,11 +657,12 @@ void GeoMapView::setup() {
|
||||
geopos.set_lat(lat_);
|
||||
geopos.set_lon(lon_);
|
||||
|
||||
geopos.on_change = [this](int32_t altitude, float lat, float lon) {
|
||||
geopos.on_change = [this](int32_t altitude, float lat, float lon, int32_t speed) {
|
||||
altitude_ = altitude;
|
||||
lat_ = lat;
|
||||
lon_ = lon;
|
||||
geopos.hide_altitude();
|
||||
speed_ = speed;
|
||||
geopos.hide_altandspeed();
|
||||
geomap.set_manual_panning(true);
|
||||
geomap.move(lon_, lat_);
|
||||
geomap.set_dirty();
|
||||
@@ -482,6 +694,7 @@ GeoMapView::GeoMapView(
|
||||
const std::string& tag,
|
||||
int32_t altitude,
|
||||
GeoPos::alt_unit altitude_unit,
|
||||
GeoPos::spd_unit speed_unit,
|
||||
float lat,
|
||||
float lon,
|
||||
uint16_t angle,
|
||||
@@ -489,6 +702,7 @@ GeoMapView::GeoMapView(
|
||||
: nav_(nav),
|
||||
altitude_(altitude),
|
||||
altitude_unit_(altitude_unit),
|
||||
speed_unit_(speed_unit),
|
||||
lat_(lat),
|
||||
lon_(lon),
|
||||
angle_(angle),
|
||||
@@ -497,8 +711,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
add_child(&geopos);
|
||||
|
||||
map_opened = geomap.init();
|
||||
if (!map_opened) return;
|
||||
geomap.init();
|
||||
|
||||
setup();
|
||||
|
||||
@@ -516,20 +729,21 @@ GeoMapView::GeoMapView(
|
||||
NavigationView& nav,
|
||||
int32_t altitude,
|
||||
GeoPos::alt_unit altitude_unit,
|
||||
GeoPos::spd_unit speed_unit,
|
||||
float lat,
|
||||
float lon,
|
||||
const std::function<void(int32_t, float, float)> on_done)
|
||||
const std::function<void(int32_t, float, float, int32_t)> on_done)
|
||||
: nav_(nav),
|
||||
altitude_(altitude),
|
||||
altitude_unit_(altitude_unit),
|
||||
speed_unit_(speed_unit),
|
||||
lat_(lat),
|
||||
lon_(lon) {
|
||||
mode_ = PROMPT;
|
||||
|
||||
add_child(&geopos);
|
||||
|
||||
map_opened = geomap.init();
|
||||
if (!map_opened) return;
|
||||
geomap.init();
|
||||
|
||||
setup();
|
||||
add_child(&button_ok);
|
||||
@@ -540,7 +754,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
button_ok.on_select = [this, on_done, &nav](Button&) {
|
||||
if (on_done)
|
||||
on_done(altitude_, lat_, lon_);
|
||||
on_done(altitude_, lat_, lon_, speed_);
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,11 +31,28 @@
|
||||
|
||||
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};
|
||||
@@ -59,19 +76,27 @@ class GeoPos : public View {
|
||||
FEET = 0,
|
||||
METERS
|
||||
};
|
||||
enum spd_unit {
|
||||
NONE = 0,
|
||||
MPH,
|
||||
KMPH,
|
||||
HIDDEN = 255
|
||||
};
|
||||
|
||||
std::function<void(int32_t, float, float)> on_change{};
|
||||
std::function<void(int32_t, float, float, int32_t)> on_change{};
|
||||
|
||||
GeoPos(const Point pos, const alt_unit altitude_unit);
|
||||
GeoPos(const Point pos, const alt_unit altitude_unit, const spd_unit speed_unit);
|
||||
|
||||
void focus() override;
|
||||
|
||||
void set_read_only(bool v);
|
||||
void set_altitude(int32_t altitude);
|
||||
void set_speed(int32_t speed);
|
||||
void set_lat(float lat);
|
||||
void set_lon(float lon);
|
||||
int32_t altitude();
|
||||
void hide_altitude();
|
||||
int32_t speed();
|
||||
void hide_altandspeed();
|
||||
float lat();
|
||||
float lon();
|
||||
|
||||
@@ -81,22 +106,35 @@ class GeoPos : public View {
|
||||
bool read_only{false};
|
||||
bool report_change{true};
|
||||
alt_unit altitude_unit_{};
|
||||
spd_unit speed_unit_{};
|
||||
|
||||
Labels labels_position{
|
||||
{{1 * 8, 0 * 16}, "Alt:", Color::light_grey()},
|
||||
{{1 * 8, 1 * 16}, "Lat: \xB0 ' \"", Color::light_grey()}, // 0xB0 is degree ° symbol in our 8x16 font
|
||||
{{1 * 8, 2 * 16}, "Lon: \xB0 ' \"", Color::light_grey()},
|
||||
};
|
||||
|
||||
Labels label_spd_position{
|
||||
{{15 * 8, 0 * 16}, "Spd:", Color::light_grey()},
|
||||
};
|
||||
NumberField field_altitude{
|
||||
{6 * 8, 0 * 16},
|
||||
5,
|
||||
{-1000, 50000},
|
||||
250,
|
||||
' '};
|
||||
|
||||
NumberField field_speed{
|
||||
{19 * 8, 0 * 16},
|
||||
4,
|
||||
{0, 5000},
|
||||
1,
|
||||
' '};
|
||||
Text text_alt_unit{
|
||||
{12 * 8, 0 * 16, 2 * 8, 16},
|
||||
""};
|
||||
Text text_speed_unit{
|
||||
{25 * 8, 0 * 16, 4 * 8, 16},
|
||||
""};
|
||||
|
||||
NumberField field_lat_degrees{
|
||||
{5 * 8, 1 * 16},
|
||||
@@ -109,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},
|
||||
""};
|
||||
@@ -131,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},
|
||||
""};
|
||||
@@ -159,6 +201,10 @@ class GeoMap : public Widget {
|
||||
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
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);
|
||||
@@ -173,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 map_zoom_line(ui::Color* buffer);
|
||||
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};
|
||||
@@ -200,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 {
|
||||
@@ -220,6 +286,7 @@ class GeoMapView : public View {
|
||||
const std::string& tag,
|
||||
int32_t altitude,
|
||||
GeoPos::alt_unit altitude_unit,
|
||||
GeoPos::spd_unit speed_unit,
|
||||
float lat,
|
||||
float lon,
|
||||
uint16_t angle,
|
||||
@@ -227,9 +294,10 @@ class GeoMapView : public View {
|
||||
GeoMapView(NavigationView& nav,
|
||||
int32_t altitude,
|
||||
GeoPos::alt_unit altitude_unit,
|
||||
GeoPos::spd_unit speed_unit,
|
||||
float lat,
|
||||
float lon,
|
||||
const std::function<void(int32_t, float, float)> on_done);
|
||||
const std::function<void(int32_t, float, float, int32_t)> on_done);
|
||||
~GeoMapView();
|
||||
|
||||
GeoMapView(const GeoMapView&) = delete;
|
||||
@@ -239,7 +307,9 @@ class GeoMapView : public View {
|
||||
|
||||
void focus() override;
|
||||
|
||||
void update_position(float lat, float lon, uint16_t angle, int32_t altitude);
|
||||
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"; };
|
||||
|
||||
@@ -253,20 +323,21 @@ class GeoMapView : public View {
|
||||
|
||||
void setup();
|
||||
|
||||
const std::function<void(int32_t, float, float)> on_done{};
|
||||
const std::function<void(int32_t, float, float, int32_t)> on_done{};
|
||||
GeoMapMode mode_{};
|
||||
int32_t altitude_{};
|
||||
int32_t speed_{};
|
||||
GeoPos::alt_unit altitude_unit_{};
|
||||
GeoPos::spd_unit speed_unit_{};
|
||||
float lat_{};
|
||||
float lon_{};
|
||||
uint16_t angle_{};
|
||||
std::function<void(void)> on_close_{nullptr};
|
||||
|
||||
bool map_opened{};
|
||||
|
||||
GeoPos geopos{
|
||||
{0, 0},
|
||||
altitude_unit_};
|
||||
altitude_unit_,
|
||||
speed_unit_};
|
||||
|
||||
GeoMap geomap{
|
||||
{0, GeoMap::banner_height, GeoMap::geomap_rect_width, GeoMap::geomap_rect_height}};
|
||||
|
||||
@@ -269,6 +269,19 @@ bool MenuView::on_key(const KeyEvent key) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MenuView::on_keyboard(const KeyboardEvent key) {
|
||||
if (key == '-') return set_highlighted(highlighted_item - 1);
|
||||
if (key == '+') return set_highlighted(highlighted_item + 1);
|
||||
if (key == 10) {
|
||||
if (menu_items[highlighted_item].on_select) {
|
||||
menu_items[highlighted_item].on_select(KeyEvent::Right);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MenuView::on_encoder(const EncoderEvent event) {
|
||||
set_highlighted(highlighted_item + event);
|
||||
return true;
|
||||
|
||||
@@ -97,6 +97,7 @@ class MenuView : public View {
|
||||
void on_blur() override;
|
||||
bool on_key(const KeyEvent event) override;
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
|
||||
private:
|
||||
void update_items();
|
||||
|
||||
@@ -93,6 +93,13 @@ void FrequencyField::set_allow_digit_mode(bool allowed) {
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencyField::getAccessibilityText(std::string& result) {
|
||||
result = to_string_dec_int(value_);
|
||||
}
|
||||
void FrequencyField::getWidgetName(std::string& result) {
|
||||
result = "FrequencyField";
|
||||
}
|
||||
|
||||
void FrequencyField::paint(Painter& painter) {
|
||||
const auto str_value = to_string_short_freq(value_);
|
||||
const auto paint_style = has_focus() ? style().invert() : style();
|
||||
@@ -165,12 +172,18 @@ bool FrequencyField::on_key(KeyEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FrequencyField::on_keyboard(KeyboardEvent key) {
|
||||
if (key == '+' || key == ' ') return on_encoder(1);
|
||||
if (key == '-') return on_encoder(-1);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FrequencyField::on_encoder(const EncoderEvent delta) {
|
||||
if (digit_mode_)
|
||||
set_value(value_ + (delta * digit_step()));
|
||||
else
|
||||
set_value(value_ + (delta * step_));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -321,6 +334,25 @@ void FrequencyKeypadView::on_button(Button& button) {
|
||||
update_text();
|
||||
}
|
||||
|
||||
bool FrequencyKeypadView::on_keyboard(const KeyboardEvent key) {
|
||||
if (key == 8) {
|
||||
digit_delete();
|
||||
update_text();
|
||||
return true;
|
||||
}
|
||||
if (key >= '0' && key <= '9') {
|
||||
digit_add(key);
|
||||
update_text();
|
||||
return true;
|
||||
}
|
||||
if (key == '.') {
|
||||
field_toggle();
|
||||
update_text();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FrequencyKeypadView::digit_add(const char c) {
|
||||
if (state == State::DigitMHz) {
|
||||
if (clear_field_if_digits_entered) {
|
||||
|
||||
@@ -61,10 +61,14 @@ class FrequencyField : public Widget {
|
||||
|
||||
bool on_key(KeyEvent event) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_keyboard(KeyboardEvent key) override;
|
||||
bool on_touch(TouchEvent event) override;
|
||||
void on_focus() override;
|
||||
void on_blur() override;
|
||||
|
||||
void getAccessibilityText(std::string& result) override;
|
||||
void getWidgetName(std::string& result) override;
|
||||
|
||||
private:
|
||||
const size_t length_;
|
||||
range_t range_;
|
||||
@@ -202,6 +206,7 @@ class FrequencyKeypadView : public View {
|
||||
rf::Frequency value() const;
|
||||
void set_value(const rf::Frequency new_value);
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_keyboard(const KeyboardEvent key) override;
|
||||
|
||||
private:
|
||||
int16_t focused_button = 0;
|
||||
|
||||
@@ -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"
|
||||
@@ -70,7 +70,7 @@
|
||||
#include "ui_settings.hpp"
|
||||
#include "ui_siggen.hpp"
|
||||
#include "ui_sonde.hpp"
|
||||
#include "ui_spectrum_painter.hpp"
|
||||
// #include "ui_spectrum_painter.hpp" //moved to ext app
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_sstvtx.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
@@ -93,7 +93,7 @@
|
||||
#include "ble_tx_app.hpp"
|
||||
#include "capture_app.hpp"
|
||||
#include "ert_app.hpp"
|
||||
#include "gps_sim_app.hpp"
|
||||
// #include "gps_sim_app.hpp" //moved to ext
|
||||
// #include "lge_app.hpp" //moved to ext
|
||||
#include "pocsag_app.hpp"
|
||||
#include "replay_app.hpp"
|
||||
@@ -111,6 +111,111 @@ namespace pmem = portapack::persistent_memory;
|
||||
|
||||
namespace ui {
|
||||
|
||||
bool CstrCmp::operator()(const char* a, const char* b) const {
|
||||
return strcmp(a, b) < 0;
|
||||
}
|
||||
|
||||
static NavigationView::AppMap generate_app_map(const NavigationView::AppList& appList) {
|
||||
NavigationView::AppMap out;
|
||||
|
||||
for (auto& app : appList) {
|
||||
if (app.id == nullptr) {
|
||||
// Skip items with no id
|
||||
continue;
|
||||
}
|
||||
|
||||
auto res = out.emplace(app.id, app);
|
||||
if (!res.second) {
|
||||
chDbgPanic("Application cannot be added, ID not unique!");
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO(u-foka): Check consistency of command names (where we add rx/tx postfix)
|
||||
const NavigationView::AppList NavigationView::appList = {
|
||||
/* HOME ******************************************************************/
|
||||
//{"playdead", "Play dead", HOME, Color::red(), &bitmap_icon_playdead, new ViewFactory<PlayDeadView>()},
|
||||
{nullptr, "Receive", HOME, Color::cyan(), &bitmap_icon_receivers, new ViewFactory<ReceiversMenuView>()},
|
||||
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
|
||||
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
|
||||
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
|
||||
{"remote", "Remote", HOME, ui::Color::green(), &bitmap_icon_remote, new ViewFactory<RemoteView>()},
|
||||
{"scanner", "Scanner", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
|
||||
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
|
||||
{nullptr, "Debug", HOME, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
|
||||
//{"about", "About", HOME, Color::cyan(), nullptr, new ViewFactory<AboutView>()},
|
||||
/* RX ********************************************************************/
|
||||
//{"acars", "ACARS", RX, Color::yellow(), &bitmap_icon_adsb, new ViewFactory<ACARSAppView>()},
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
|
||||
{"aprsrx", "APRS", RX, Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSRXView>()},
|
||||
{"audio", "Audio", RX, Color::green(), &bitmap_icon_speaker, new ViewFactory<AnalogAudioView>()},
|
||||
//{"btle", "BTLE", RX, Color::yellow(), &bitmap_icon_btle, new ViewFactory<BTLERxView>()},
|
||||
//{"blecomm", "BLE Comm", RX, ui::Color::orange(), &bitmap_icon_btle, new ViewFactory<BLECommView>()},
|
||||
{"blerx", "BLE Rx", RX, Color::green(), &bitmap_icon_btle, new ViewFactory<BLERxView>()},
|
||||
{"ert", "ERT Meter", RX, Color::green(), &bitmap_icon_ert, new ViewFactory<ERTAppView>()},
|
||||
{"level", "Level", RX, Color::green(), &bitmap_icon_options_radio, new ViewFactory<LevelView>()},
|
||||
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
|
||||
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
|
||||
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"tmps", "TPMS Cars", RX, Color::green(), &bitmap_icon_tpms, new ViewFactory<TPMSAppView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
//{"fskrx", "FSK RX", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<FskxRxMainView>()},
|
||||
//{"dmr", "DMR", RX, Color::dark_grey(), &bitmap_icon_dmr, new ViewFactory<NotImplementedView>()},
|
||||
//{"sigfox", "SIGFOX", RX, Color::dark_grey(), &bitmap_icon_fox, new ViewFactory<NotImplementedView>()},
|
||||
//{"lora", "LoRa", RX, Color::dark_grey(), &bitmap_icon_lora, new ViewFactory<NotImplementedView>()},
|
||||
//{"sstv", "SSTV", RX, Color::dark_grey(), &bitmap_icon_sstv, new ViewFactory<NotImplementedView>()},
|
||||
//{"tetra", "TETRA", RX, Color::dark_grey(), &bitmap_icon_tetra, new ViewFactory<NotImplementedView>()},
|
||||
/* TX ********************************************************************/
|
||||
{"adsbtx", "ADS-B TX", TX, ui::Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBTxView>()},
|
||||
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
|
||||
{"bht", "BHT Xy/EP", TX, ui::Color::green(), &bitmap_icon_bht, new ViewFactory<BHTView>()},
|
||||
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
|
||||
{"morse", "Morse", TX, ui::Color::green(), &bitmap_icon_morse, new ViewFactory<MorseView>()},
|
||||
//{"nuoptixdtmf", "Nuoptix DTMF", TX, ui::Color::green(), &bitmap_icon_nuoptix, new ViewFactory<NuoptixView>()},
|
||||
{"ooktx", "OOK", TX, ui::Color::yellow(), &bitmap_icon_remote, new ViewFactory<EncodersView>()},
|
||||
{"pocsagtx", "POCSAG TX", TX, ui::Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGTXView>()},
|
||||
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
|
||||
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
|
||||
{"sstvtx", "SSTV", TX, ui::Color::green(), &bitmap_icon_sstv, new ViewFactory<SSTVTXView>()},
|
||||
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
|
||||
/* UTILITIES *************************************************************/
|
||||
{"antennalength", "Antenna Length", UTILITIES, Color::green(), &bitmap_icon_tools_antenna, new ViewFactory<WhipCalcView>()},
|
||||
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
|
||||
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
|
||||
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
|
||||
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
//{"tonesearch", "Tone Search", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<ToneSearchView>()},
|
||||
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_temperature, new ViewFactory<FlashUtilityView>()},
|
||||
{nullptr, "Wipe SD card", UTILITIES, Color::red(), &bitmap_icon_tools_wipesd, new ViewFactory<WipeSDView>()},
|
||||
};
|
||||
|
||||
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
|
||||
|
||||
bool NavigationView::StartAppByName(const char* name) {
|
||||
home(false);
|
||||
|
||||
auto it = appMap.find(name);
|
||||
if (it != appMap.end()) {
|
||||
push_view(std::unique_ptr<View>(it->second.viewFactory->produce(*this)));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* StatusTray ************************************************************/
|
||||
|
||||
StatusTray::StatusTray(Point pos)
|
||||
@@ -398,8 +503,8 @@ void SystemStatusView::rtc_battery_workaround() {
|
||||
month = (timestamp.FAT_date >> 5) & 0xF;
|
||||
day = timestamp.FAT_date & 0x1F;
|
||||
|
||||
// bump to next month at 28 days for simplicity
|
||||
if (++day > 28) {
|
||||
// bump to next month
|
||||
if (++day > rtc_time::days_per_month(year, month)) {
|
||||
day = 1;
|
||||
if (++month > 12) {
|
||||
month = 1;
|
||||
@@ -407,7 +512,7 @@ void SystemStatusView::rtc_battery_workaround() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
make_new_file(DATE_FILEFLAG);
|
||||
|
||||
year = 1980;
|
||||
@@ -441,7 +546,17 @@ InformationView::InformationView(
|
||||
&version,
|
||||
<ime});
|
||||
|
||||
#if GCC_VERSION_MISMATCH
|
||||
version.set_style(&Styles::yellow);
|
||||
#else
|
||||
version.set_style(&style_infobar);
|
||||
#endif
|
||||
|
||||
if (firmware_checksum_error()) {
|
||||
version.set("FLASH ERROR");
|
||||
version.set_style(&Styles::red);
|
||||
}
|
||||
|
||||
ltime.set_style(&style_infobar);
|
||||
refresh();
|
||||
set_dirty();
|
||||
@@ -453,6 +568,17 @@ void InformationView::refresh() {
|
||||
ltime.set_date_enabled(pmem::clock_with_date());
|
||||
}
|
||||
|
||||
bool InformationView::firmware_checksum_error() {
|
||||
static bool fw_checksum_checked{false};
|
||||
static bool fw_checksum_error{false};
|
||||
|
||||
// only checking firmware checksum once per boot
|
||||
if (!fw_checksum_checked) {
|
||||
fw_checksum_error = (simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE) != FLASH_EXPECTED_CHECKSUM);
|
||||
}
|
||||
return fw_checksum_error;
|
||||
}
|
||||
|
||||
/* Navigation ************************************************************/
|
||||
|
||||
bool NavigationView::is_top() const {
|
||||
@@ -487,6 +613,14 @@ void NavigationView::pop(bool trigger_update) {
|
||||
if (on_pop) on_pop();
|
||||
}
|
||||
|
||||
void NavigationView::home(bool trigger_update) {
|
||||
while (view_stack.size() > 1) {
|
||||
pop(false);
|
||||
}
|
||||
|
||||
if (trigger_update) update_view();
|
||||
}
|
||||
|
||||
void NavigationView::display_modal(
|
||||
const std::string& title,
|
||||
const std::string& message) {
|
||||
@@ -546,43 +680,47 @@ bool NavigationView::set_on_pop(std::function<void()> on_pop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Helpers **************************************************************/
|
||||
|
||||
static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.menuLocation == loc) {
|
||||
grid.add_item({app.displayName, app.iconColor, app.icon,
|
||||
[&nav, &app]() { nav.push_view(std::unique_ptr<View>(app.viewFactory->produce(nav))); }});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void addExternalItems(NavigationView& nav, app_location_t location, BtnGridView& grid) {
|
||||
auto externalItems = ExternalItemsMenuLoader::load_external_items(location, nav);
|
||||
if (externalItems.empty()) {
|
||||
grid.add_item({"Notice",
|
||||
Color::red(),
|
||||
&bitmap_icon_debug,
|
||||
[&nav]() {
|
||||
nav.display_modal(
|
||||
"Notice",
|
||||
"External app directory empty;\n"
|
||||
"see Mayhem wiki and copy apps\n"
|
||||
"to APPS folder of SD card.");
|
||||
}});
|
||||
} else {
|
||||
for (auto const& gridItem : externalItems) {
|
||||
grid.add_item(gridItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ReceiversMenuView *****************************************************/
|
||||
|
||||
ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
add_item({"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }});
|
||||
}
|
||||
add_items({
|
||||
// {"ACARS", Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push<ACARSAppView>(); }},
|
||||
{"ADS-B", Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBRxView>(); }},
|
||||
{"AIS Boats", Color::green(), &bitmap_icon_ais, [&nav]() { nav.push<AISAppView>(); }},
|
||||
//{"Analog TV", Color::yellow(), &bitmap_icon_sstv, [&nav]() { nav.push<AnalogTvView>(); }}, //moved to ext
|
||||
{"APRS", Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSRXView>(); }},
|
||||
{"Audio", Color::green(), &bitmap_icon_speaker, [&nav]() { nav.push<AnalogAudioView>(); }},
|
||||
//{"BTLE", Color::yellow(), &bitmap_icon_btle, [&nav]() { nav.push<BTLERxView>(); }},
|
||||
//{"BLE Comm", ui::Color::orange(), &bitmap_icon_btle, [&nav]() { nav.push<BLECommView>(); }},
|
||||
{"BLE Rx", Color::green(), &bitmap_icon_btle, [&nav]() { nav.push<BLERxView>(); }},
|
||||
{"ERT Meter", Color::green(), &bitmap_icon_ert, [&nav]() { nav.push<ERTAppView>(); }},
|
||||
{"Level", Color::green(), &bitmap_icon_options_radio, [&nav]() { nav.push<LevelView>(); }},
|
||||
//{"NRF", Color::yellow(), &bitmap_icon_nrf, [&nav]() { nav.push<NRFRxView>(); }}, //moved to ext
|
||||
{"POCSAG", Color::green(), &bitmap_icon_pocsag, [&nav]() { nav.push<POCSAGAppView>(); }},
|
||||
{"Radiosnde", Color::green(), &bitmap_icon_sonde, [&nav]() { nav.push<SondeView>(); }},
|
||||
{"Recon", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ReconView>(); }},
|
||||
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
|
||||
{"TPMS Cars", Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push<TPMSAppView>(); }},
|
||||
{"Weather", Color::green(), &bitmap_icon_thermometer, [&nav]() { nav.push<WeatherView>(); }},
|
||||
{"SubGhzD", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<SubGhzDView>(); }},
|
||||
// {"FSK RX", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<FskxRxMainView>(); }},
|
||||
// {"DMR", Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"SIGFOX", Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"LoRa", Color::dark_grey(), &bitmap_icon_lora, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"SSTV", Color::dark_grey(), &bitmap_icon_sstv, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"TETRA", Color::dark_grey(), &bitmap_icon_tetra, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
});
|
||||
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::RX, nav)) {
|
||||
add_item(gridItem);
|
||||
};
|
||||
add_apps(nav, *this, RX);
|
||||
|
||||
addExternalItems(nav, app_location_t::RX, *this);
|
||||
}
|
||||
|
||||
/* TransmittersMenuView **************************************************/
|
||||
@@ -591,31 +729,10 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"ADS-B TX", ui::Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBTxView>(); }},
|
||||
{"APRS TX", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSTXView>(); }},
|
||||
{"BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav]() { nav.push<BHTView>(); }},
|
||||
{"BLE Tx", ui::Color::green(), &bitmap_icon_btle, [&nav]() { nav.push<BLETxView>(); }},
|
||||
// {"BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav]() { nav.push<CoasterPagerView>(); }}, //moved to ext
|
||||
{"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }},
|
||||
//{"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 *****************************************************/
|
||||
@@ -624,26 +741,10 @@ UtilitiesMenuView::UtilitiesMenuView(NavigationView& nav) {
|
||||
if (pmem::show_gui_return_icon()) {
|
||||
add_items({{"..", Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"Antenna Length", Color::green(), &bitmap_icon_tools_antenna, [&nav]() { nav.push<WhipCalcView>(); }},
|
||||
{"File Manager", Color::green(), &bitmap_icon_dir, [&nav]() { nav.push<FileManagerView>(); }},
|
||||
{"Freq. Manager", Color::green(), &bitmap_icon_freqman, [&nav]() { nav.push<FrequencyManagerView>(); }},
|
||||
{"Notepad", Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<TextEditorView>(); }},
|
||||
{"IQ Trim", Color::orange(), &bitmap_icon_trim, [&nav]() { nav.push<IQTrimView>(); }},
|
||||
{"SD Over USB", Color::yellow(), &bitmap_icon_hackrf, [&nav]() { nav.push<SdOverUsbView>(); }},
|
||||
{"Signal Gen", Color::green(), &bitmap_icon_cwgen, [&nav]() { nav.push<SigGenView>(); }},
|
||||
// {"Test App", Color::dark_grey(), nullptr, [&nav](){ nav.push<TestView>(); }},
|
||||
// {"Tone Search", Color::dark_grey(), nullptr, [&nav](){ nav.push<ToneSearchView>(); }},
|
||||
{"Wav View", Color::yellow(), &bitmap_icon_soundboard, [&nav]() { nav.push<ViewWavView>(); }},
|
||||
|
||||
// Dangerous apps.
|
||||
{"Flash Utility", Color::red(), &bitmap_icon_temperature, [&nav]() { nav.push<FlashUtilityView>(); }},
|
||||
{"Wipe SD card", Color::red(), &bitmap_icon_tools_wipesd, [&nav]() { nav.push<WipeSDView>(); }},
|
||||
});
|
||||
add_apps(nav, *this, UTILITIES);
|
||||
|
||||
for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::UTILITIES, nav)) {
|
||||
add_item(gridItem);
|
||||
};
|
||||
addExternalItems(nav, app_location_t::UTILITIES, *this);
|
||||
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
@@ -663,22 +764,9 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) {
|
||||
}
|
||||
|
||||
SystemMenuView::SystemMenuView(NavigationView& nav) {
|
||||
add_items({
|
||||
// {"Play dead", Color::red(), &bitmap_icon_playdead, [&nav]() { nav.push<PlayDeadView>(); }},
|
||||
{"Receive", Color::cyan(), &bitmap_icon_receivers, [&nav]() { nav.push<ReceiversMenuView>(); }},
|
||||
{"Transmit", Color::cyan(), &bitmap_icon_transmit, [&nav]() { nav.push<TransmittersMenuView>(); }},
|
||||
{"Capture", Color::red(), &bitmap_icon_capture, [&nav]() { nav.push<CaptureAppView>(); }},
|
||||
{"Replay", Color::green(), &bitmap_icon_replay, [&nav]() { nav.push<PlaylistView>(); }},
|
||||
{"Remote", ui::Color::green(), &bitmap_icon_remote, [&nav]() { nav.push<RemoteView>(); }},
|
||||
{"Scanner", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ScannerView>(); }},
|
||||
{"Microphone", Color::green(), &bitmap_icon_microphone, [&nav]() { nav.push<MicTXView>(); }},
|
||||
{"Looking Glass", Color::green(), &bitmap_icon_looking, [&nav]() { nav.push<GlassView>(); }},
|
||||
{"Utilities", Color::cyan(), &bitmap_icon_utilities, [&nav]() { nav.push<UtilitiesMenuView>(); }},
|
||||
{"Settings", Color::cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SettingsMenuView>(); }},
|
||||
{"Debug", Color::light_grey(), &bitmap_icon_debug, [&nav]() { nav.push<DebugMenuView>(); }},
|
||||
{"HackRF", Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }},
|
||||
// {"About", Color::cyan(), nullptr, [&nav]() { nav.push<AboutView>(); }},
|
||||
});
|
||||
add_apps(nav, *this, HOME);
|
||||
|
||||
add_item({"HackRF", Color::cyan(), &bitmap_icon_hackrf, [this, &nav]() { hackrf_mode(nav); }});
|
||||
|
||||
set_max_rows(2); // allow wider buttons
|
||||
set_arrow_enabled(false);
|
||||
@@ -741,6 +829,9 @@ SystemView::SystemView(
|
||||
Context& SystemView::context() const {
|
||||
return context_;
|
||||
}
|
||||
NavigationView* SystemView::get_navigation_view() {
|
||||
return &navigation_view;
|
||||
}
|
||||
|
||||
void SystemView::toggle_overlay() {
|
||||
switch (++overlay_active) {
|
||||
@@ -748,7 +839,7 @@ void SystemView::toggle_overlay() {
|
||||
this->add_child(&this->overlay);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 1;
|
||||
shared_memory.m4_cpu_usage = 0;
|
||||
shared_memory.m4_performance_counter = 0;
|
||||
shared_memory.m4_heap_usage = 0;
|
||||
shared_memory.m4_stack_usage = 0;
|
||||
break;
|
||||
@@ -756,11 +847,12 @@ void SystemView::toggle_overlay() {
|
||||
this->remove_child(&this->overlay);
|
||||
this->add_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
shared_memory.request_m4_performance_counter = 2;
|
||||
break;
|
||||
case 3:
|
||||
this->remove_child(&this->overlay2);
|
||||
this->set_dirty();
|
||||
shared_memory.request_m4_performance_counter = 0;
|
||||
overlay_active = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
#ifndef __UI_NAVIGATION_H__
|
||||
#define __UI_NAVIGATION_H__
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_focus.hpp"
|
||||
@@ -40,9 +44,8 @@
|
||||
#include "diskio.h"
|
||||
#include "lfsr_random.hpp"
|
||||
#include "sd_card.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "external_app.hpp"
|
||||
#include "view_factory.hpp"
|
||||
|
||||
// for incrementing fake date when RTC battery is dead
|
||||
#define DATE_FILEFLAG u"/SETTINGS/DATE_FILEFLAG"
|
||||
@@ -57,6 +60,28 @@ enum modal_t {
|
||||
ABORT
|
||||
};
|
||||
|
||||
class CstrCmp {
|
||||
public:
|
||||
bool operator()(const char* a, const char* b) const;
|
||||
};
|
||||
|
||||
// Should only be used as part of the appList in NavigationView, the viewFactory will never be destroyed.
|
||||
class AppInfo {
|
||||
public:
|
||||
const char* id; // MUST be unique! Used by serial command to start the app so it also has to make sense
|
||||
const char* displayName;
|
||||
app_location_t menuLocation;
|
||||
Color iconColor;
|
||||
const Bitmap* icon;
|
||||
ViewFactoryBase* viewFactory; // Never destroyed, and I believe it's ok ;) Having a unique_ptr here breaks the initializer list of appList
|
||||
};
|
||||
|
||||
struct AppInfoConsole {
|
||||
const char* appCallName;
|
||||
const char* appFriendlyName;
|
||||
const app_location_t appLocation;
|
||||
};
|
||||
|
||||
class NavigationView : public View {
|
||||
public:
|
||||
std::function<void(const View&)> on_view_changed{};
|
||||
@@ -85,6 +110,7 @@ class NavigationView : public View {
|
||||
View* push_view(std::unique_ptr<View> new_view);
|
||||
void replace(View* v);
|
||||
void pop(bool trigger_update = true);
|
||||
void home(bool trigger_update);
|
||||
|
||||
void display_modal(const std::string& title, const std::string& message);
|
||||
void display_modal(
|
||||
@@ -99,6 +125,14 @@ class NavigationView : public View {
|
||||
* Returns true if the handler was bound successfully. */
|
||||
bool set_on_pop(std::function<void()> on_pop);
|
||||
|
||||
// App list is used to preserve order, so the menu items in the menu grid can stay in place
|
||||
// App map is used to look up apps by id used by serial app start
|
||||
using AppMap = std::map<const char*, const AppInfo&, CstrCmp>;
|
||||
using AppList = std::vector<AppInfo>;
|
||||
static const AppMap appMap;
|
||||
static const AppList appList;
|
||||
|
||||
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
|
||||
private:
|
||||
struct ViewState {
|
||||
std::unique_ptr<View> view;
|
||||
@@ -252,6 +286,7 @@ class InformationView : public View {
|
||||
public:
|
||||
InformationView(NavigationView& nav);
|
||||
void refresh();
|
||||
bool firmware_checksum_error();
|
||||
|
||||
private:
|
||||
// static constexpr auto version_string = "v1.4.4"; // This is commented out as we are now setting the version via ENV (VERSION_STRING=v1.0.0)
|
||||
@@ -321,6 +356,8 @@ class SystemView : public View {
|
||||
void toggle_overlay();
|
||||
void paint_overlay();
|
||||
|
||||
NavigationView* get_navigation_view();
|
||||
|
||||
private:
|
||||
uint8_t overlay_active{0};
|
||||
|
||||
|
||||
@@ -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() {
|
||||
@@ -32,10 +34,14 @@ void USBSerial::dispatch() {
|
||||
|
||||
if (shell_created == false) {
|
||||
shell_created = true;
|
||||
create_shell();
|
||||
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() {
|
||||
|
||||
@@ -24,14 +24,18 @@
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
class EventDispatcher;
|
||||
|
||||
namespace portapack {
|
||||
|
||||
class USBSerial {
|
||||
public:
|
||||
void initialize();
|
||||
void dispatch();
|
||||
void dispatch_transfer();
|
||||
void on_channel_opened();
|
||||
void on_channel_closed();
|
||||
void setEventDispatcher(EventDispatcher* ed) { _eventDispatcher = ed; }
|
||||
|
||||
private:
|
||||
void enable_xtal();
|
||||
@@ -43,6 +47,8 @@ class USBSerial {
|
||||
|
||||
bool connected{false};
|
||||
bool shell_created{false};
|
||||
|
||||
EventDispatcher* _eventDispatcher = NULL;
|
||||
};
|
||||
|
||||
} // namespace portapack
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,4 +26,6 @@
|
||||
|
||||
#include "shell.h"
|
||||
|
||||
void create_shell(void);
|
||||
class EventDispatcher;
|
||||
|
||||
void create_shell(EventDispatcher* evtd);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user