Compare commits

...

38 Commits

Author SHA1 Message Date
E.T 75ece38725 Dockerfile overhaul (#1726)
Create entrypoint to orchestrate the build steps
Supported commands: make, ninja
Passes additional arguments to the make / ninja command at the end (like -jNN)
There is a shortcut to make -jNN by just specifying -jNN
Anything else will be directly executed (like getting a shell into the container with bash -li is still possible)
2024-01-06 00:42:36 +01:00
E.T b53032a8a5 Ppfw docker build (#1725)
* Docker build ppfw by default

* Allow GH workflows to be triggered from any branch

* Fix asset path for github upload
2024-01-05 21:59:15 +01:00
Mark Thompson e7f8952ece Fix compile error in deprecated app (#1723) 2024-01-05 10:13:43 -06:00
Totoo 82a6ae0791 Geomap speed display (#1722)
* speed to geomap
* Add speed to map
* Fix hidden state
* UI fix on ADSB tx
* UI fixes
2024-01-05 13:44:30 +01:00
Totoo d303098e35 Moved SPainter and GPSSim to ext app (#1721)
* Moved GPSSim
* Moved spainter
2024-01-05 10:46:09 +01:00
Totoo 1a69ce2d97 Accessibility over serial (#1717)
* Initial accessibility support
* added it to some widgets to test
* More widget accessibility response
* More widgets, better output
* Mark selected widget on list
* typo
2024-01-05 07:43:30 +01:00
Erwin Ried eedebe1c52 Update your links (#1719) 2024-01-05 07:43:13 +01:00
E.T 9e61f80809 Create cmake target ppfw (#1716)
* Create cmake target ppfw and oci

It will build an OCI ppfw package to be flashed or shared on test-drive

Additionally some minor cmake cleanup
And updated gitignore to allow multiple build folders

* Make ppfw / oci build default
2024-01-04 22:59:25 +01:00
Mark Thompson ff591c68a0 Updated Flash message to say device will restart (#1715) 2024-01-04 11:47:45 -06:00
Totoo 8761b9d7e0 Send ASCII chars from USB serial to selected widget (#1708)
* Initial commit for keyboard emulation
* Added on_keyboard to some widgets
* TextEdit partly
* Multi key send at once
* Frequency control support
* Fix encoder emulation
* Add keyboard to geomap
* More widgets
2024-01-04 17:36:31 +01:00
Erwin Ried 1b5125b0a8 Adding the new TechMinds video (#1712)
I think it explains very well everything, even if he calls it "Portapak"
2024-01-04 11:28:33 +01:00
E.T 9ba885361d add _OCI to ppfw package name (#1711) 2024-01-04 20:52:42 +13:00
Mark Thompson afa34d83b2 Added Zoom-Out capability to Geomap (#1710) 2024-01-04 07:32:44 +01:00
E.T d93c914dd3 Create firmware ppfw.tar to flash firmware together with apps (#1709)
* Github actions test

* Action to create and upload ppfw.tar

+remove unused travis config

* Changelog token fix

* Also generate ppfw for stable releases

* Make tar verbose
2024-01-03 22:32:34 +01:00
Mark Thompson 715a2dd448 Add Tamper Flags to ERT RX display and meter ID to log file (#1707) 2024-01-03 20:17:33 +01:00
Totoo 58bf60695d Touch emulation from usb cdc (#1706) 2024-01-03 14:06:29 +01:00
Totoo fbe7954f2e Added support for extracting firmware from TAR file (with apps) (#1704)
* Added support for extracting firmware from TAR file (with apps)
* Added tar to usb cdc firmware command.
* Serial flash tar
* Show error on bad tar
* Check tar for valid filenames
2024-01-03 14:05:24 +01:00
tomvandermussele d122a8fc3b Created Belgian Airband file (#1705)
* Created file in FREQMAN for the Belgian Airband (Brussels Airport)

* Added Military to BE Airband
2024-01-03 12:02:04 +01:00
Bernd Herzog 3998dc124a added cpld info shell commands (#1703)
* added cpld info shell commands

* fixed reset
2024-01-02 00:18:53 +01:00
jLynx 3d2da9c0db Serial info command (#1700)
* WIP adding more info

* Added source output & R9 detection

* WIP

* WIP

* Moved hackrf back

* Fixed output error
2024-01-02 07:25:36 +13:00
Erwin Ried 3189d3af4c Update FUNDING.yml (#1702)
Removing bountysource, since they scammed everyone: https://github.com/bountysource/core/issues/1586
2024-01-01 15:14:37 +01:00
E.T 1b3a99cc5e Gcc warning onscreen (#1701)
* Fix GCC version detection for old CMAKE

* Yellow tint version number / hash on gcc mismatch
2023-12-31 15:46:26 -06:00
E.T 935c43e88f Gcc version warning (#1698)
* Compiler warning
* Fix warning message
2023-12-31 16:36:19 +01:00
gullradriel 2893c031ab Recon repeat delay (#1699)
- Recon repeat delay, range: from 0s to 254s, timer is played before each TX
- fixed missing '<' '>' around button names for DELETE and REMOVE name to indicate that they can also be used to skip in the list
- added missing pmem recon dump entries
2023-12-30 22:32:41 +01:00
E.T 94cdb16ca9 Mention external apps in stable release notes (#1694) 2023-12-29 15:11:40 -06:00
jLynx 84d4066986 V1.9.1 (#1691)
* Update past_version.txt

* Update version.txt
2023-12-29 22:07:43 +13:00
Mark Thompson 2153c2dd10 ERT app - Fix freq field overlapping amp setting (#1689) 2023-12-29 07:28:42 +01:00
gullradriel a7393c3492 fix for freq_b equal zero in repeater entries (#1686) 2023-12-28 20:10:38 +01:00
gullradriel 794fece8cc Recon raw repeat (#1658)
* UI for Recon Repeater config
* persistent recon repeat settings
* record_view: added possibility to force keep the provided record filename
* working auto record to same file
* Recon TX 
* added Repeater type
* adding yellow coloring on Repeater config+a modal, comments in the code
* default repeater values
2023-12-28 11:25:53 +01:00
Totoo 1bf95e85a0 Fixed #1670 (#1676) 2023-12-26 07:16:54 +01:00
Brumi-2021 a85357a8af Minor Improvement (Extending_min_CLK_out_from_10khz_to_4khz) (#1675) 2023-12-26 07:11:14 +01:00
Totoo 7bf3e02f6c Faster usb serial file download (#1674)
* Made file read much faster via USB serial
* Remove old code
2023-12-25 17:25:16 +01:00
Mark Thompson 459e8d0b24 Disallow CLKOUT freq changes on r9 (#1672) 2023-12-24 17:28:22 +01:00
Mark Thompson 93585d846a CLKOUT workaround for r9 boards (#1671) 2023-12-24 11:06:11 +01:00
Mark Thompson 4eb5c4603e Simplify CLKOUT frequency entry (#1668) 2023-12-24 08:55:15 +01:00
Mark Thompson f42ad5ffc6 Configure CLKOUT at power-up per PMEM setting (#1669) 2023-12-24 08:54:56 +01:00
gullradriel d0bd8e50be Added USB serial checkbox (#1664) 2023-12-22 11:00:27 +01:00
Mark Thompson 337e3f4449 Enhance Debug->Peripherals to view all registers of SI5351 chip (#1663) 2023-12-22 08:45:05 +01:00
105 changed files with 3682 additions and 963 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
custom: # https://salt.bountysource.com/checkout/amount?team=portapack-mayhem
+17 -2
View File
@@ -36,7 +36,12 @@ jobs:
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: |
@@ -64,7 +69,7 @@ jobs:
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
- name: Create changelog
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGELOG=$(python3 .github/workflows/changelog.py)
CHANGELOG="${CHANGELOG//'%'/'%25'}"
@@ -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.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
+20 -4
View File
@@ -14,7 +14,12 @@ jobs:
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: |
@@ -48,7 +53,7 @@ jobs:
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
- 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'}"
@@ -74,13 +79,24 @@ jobs:
**Full Changelog**: https://github.com/eried/portapack-mayhem/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/eried/portapack-havoc/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.ppfw.tar
asset_name: mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar
asset_content_type: application/x-tar
- name: Upload Firmware Asset
id: upload-firmware-asset
uses: actions/upload-release-asset@v1
+1 -1
View File
@@ -1 +1 @@
v1.8.0
v1.9.0
+1 -1
View File
@@ -1 +1 @@
v1.9.0
v1.9.1
+1 -1
View File
@@ -49,7 +49,7 @@
*.map
*.lst
.dep/
build/
/build*
CMakeFiles/
# Debugging
-55
View File
@@ -1,55 +0,0 @@
language: cpp
matrix:
include:
- os: linux
compiler: gcc
cache: apt
dist: xenial
env:
global:
- PROJECT_NAME=PortaPack-HAVOC
- SHORT_COMMIT_HASH=`git rev-parse --short HEAD`
- VERSION_STRING=nightly-$SHORT_COMMIT_HASH
- BUILD_DATE="`date +%Y-%m-%d`"
- BUILD_NAME="$PROJECT_NAME-$BUILD_DATE-$SHORT_COMMIT_HASH"
- ARTEFACT_BASE=$TRAVIS_BUILD_DIR/artefacts/
- ARTEFACT_PATH=$ARTEFACT_BASE/$BUILD_NAME
before_install:
- sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa -y
- sudo apt-get update -q
- sudo apt-get install gcc-arm-embedded -y
script:
# TODO: Introduce top-level Makefile, this is lame.
- sed -e "s/\#set(VERSION.*/set(VERSION \"$VERSION_STRING\")/" -i".bak" CMakeLists.txt
- mkdir build/
- pushd build/
- cmake ..
- make firmware
- popd
after_success:
- mkdir -p $ARTEFACT_PATH
# Copy firmware to firmware-bin directory
- cd $TRAVIS_BUILD_DIR/build
- cp firmware/portapack-h1-havoc.bin $ARTEFACT_PATH/
- cp hackrf/firmware/hackrf_usb/hackrf_usb.dfu $ARTEFACT_PATH/
- cd $TRAVIS_BUILD_DIR
- cp LICENSE $ARTEFACT_PATH/
# Build the archive
- cd $ARTEFACT_BASE
- tar -cJvf $ARTEFACT_BASE/$BUILD_NAME.tar.xz $BUILD_NAME
- md5sum --binary $BUILD_NAME.tar.xz >MD5SUMS
- sha256sum --binary $BUILD_NAME.tar.xz >SHA256SUMS
addons:
apt:
packages:
- coreutils
- tar
- sed
- cmake
- dfu-util
+21 -2
View File
@@ -18,15 +18,17 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
project(portapack-h1)
set(EXPECTED_GCC_VERSION "9.2.1")
set(VERSION "$ENV{VERSION_STRING}")
if ("$ENV{VERSION_STRING}" STREQUAL "")
if ("${VERSION}" STREQUAL "")
execute_process(
COMMAND git log -n 1 --format=%h
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
@@ -40,6 +42,11 @@ 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}")
set(PPFW_FILENAME "portapack-mayhem.ppfw.tar")
endif()
execute_process(
@@ -48,6 +55,18 @@ execute_process(
)
set(VERSION_MD5 "0x${VERSION_MD5}")
if (NOT DEFINED PPFW_FILENAME)
# Funny business here: trying to generate unique names to prevent renaming of shared test-drive builds by the browser on download to .ppfw-42.tar as it wont be recognized by the flasher
execute_process(
COMMAND date "+%y%m%d-%H%M"
OUTPUT_VARIABLE PPFW_FILENAME
)
string(STRIP ${PPFW_FILENAME} PPFW_FILENAME)
set(PPFW_FILENAME "portapack-mayhem_${PPFW_FILENAME}_OCI.ppfw.tar")
endif()
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
set(HARDWARE_PATH ${CMAKE_CURRENT_LIST_DIR}/hardware)
+6 -1
View File
@@ -1,3 +1,6 @@
> [!IMPORTANT]
> *Shortly*, this repository **will be moved** from my personal GitHub [eried/portapack-mayhem](https://github.com/eried/portapack-mayhem) to an organization: [portapack-mayhem/portapack-mayhem](https://github.com/portapack-mayhem/portapack-mayhem). Please keep this in mind to **update your links** accordingly.
# PortaPack Mayhem
[![Build Status](https://travis-ci.com/eried/portapack-mayhem.svg?branch=master)](https://travis-ci.com/eried/portapack-mayhem) [![Nightly Release](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml/badge.svg?branch=next)](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [![CodeScene Code Health](https://codescene.io/projects/8381/status-badges/code-health)](https://codescene.io/projects/8381) [![GitHub All Releases](https://img.shields.io/github/downloads/eried/portapack-mayhem/total)](https://github.com/eried/portapack-mayhem/releases) [![GitHub Releases](https://img.shields.io/github/downloads/eried/portapack-mayhem/latest/total)](https://github.com/eried/portapack-mayhem/releases/latest) [![Docker Hub Pulls](https://img.shields.io/docker/pulls/eried/portapack.svg)](https://hub.docker.com/r/eried/portapack) [![Discord Chat](https://dcbadge.vercel.app/api/server/tuwVMv3?style=flat)](https://discord.gg/tuwVMv3)
@@ -12,7 +15,9 @@ This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmw
If you are new to *HackRF+PortaPack+Mayhem*, check this video:
[![What is?](https://img.youtube.com/vi/alrFbY5vxt4/0.jpg)](https://www.youtube.com/watch?v=alrFbY5vxt4)
[![Beginner's Guide To The HackRF & Portapak With Mayhem](https://img.youtube.com/vi/H-bqdWfbhpg/0.jpg)](https://www.youtube.com/watch?v=H-bqdWfbhpg)
For people familiar with the [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware), this one might be interesting:<br>[What is the HackRF One Portapack H2+?](https://www.youtube.com/watch?v=alrFbY5vxt4)
# Frequently Asked Questions
+22 -14
View File
@@ -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"]
+1 -1
View File
@@ -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
View File
@@ -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"]
+49 -1
View File
@@ -18,6 +18,8 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.5)
project(firmware)
set(BASEBAND ${PROJECT_SOURCE_DIR}/baseband)
@@ -34,6 +36,17 @@ set(LZ4 lz4)
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
# 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)
add_subdirectory(test)
@@ -48,10 +61,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 +90,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
# 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 +124,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
)
+6 -6
View File
@@ -122,7 +122,6 @@ set(CSRC
${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
@@ -265,7 +265,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 +311,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
@@ -424,7 +424,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)
+2 -1
View File
@@ -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() {
+15 -5
View File
@@ -426,6 +426,7 @@ BLERxView::BLERxView(NavigationView& nav)
&options_sort,
&label_found,
&text_found_count,
&check_serial_log,
&button_filter,
&button_save_list,
&button_clear_list,
@@ -436,7 +437,15 @@ BLERxView::BLERxView(NavigationView& nav)
nav_.push<BleRecentEntryDetailView>(entry);
};
usb_serial_thread = std::make_unique<UsbSerialThread>();
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
usb_serial_thread = std::make_unique<UsbSerialThread>();
} else {
usb_serial_thread.reset();
}
};
check_serial_log.set_value(serial_logging);
ensure_directory(find_packet_path);
ensure_directory(log_packets_path);
@@ -456,8 +465,6 @@ BLERxView::BLERxView(NavigationView& nav)
logger = std::make_unique<BLELogger>();
check_log.set_value(logging);
check_log.on_select = [this](Checkbox&, bool v) {
str_log = "";
logging = v;
@@ -468,6 +475,7 @@ BLERxView::BLERxView(NavigationView& nav)
"/BLELOG_" +
to_string_timestamp(rtc_time::now()) + ".TXT");
};
check_log.set_value(logging);
button_save_list.on_select = [this, &nav](const ui::Button&) {
listFileBuffer = "";
@@ -723,8 +731,10 @@ void BLERxView::on_data(BlePacketData* packet) {
logger->log_raw_data(str_console + "\r\n");
}
usb_serial_thread->serial_str = str_console + "\r\n";
usb_serial_thread->str_ready = true;
if (serial_logging) {
usb_serial_thread->serial_str = str_console + "\r\n";
usb_serial_thread->str_ready = true;
}
str_console = "";
if (!searchList.empty()) {
+9
View File
@@ -215,6 +215,7 @@ class BLERxView : public View {
uint8_t sort_index{0};
std::string filter{};
bool logging{false};
bool serial_logging{false};
bool name_enable{true};
app_settings::SettingsManager settings_{
@@ -225,6 +226,8 @@ class BLERxView : public View {
{"sort_index"sv, &sort_index},
{"filter"sv, &filter},
{"log"sv, &logging},
// disabled to always start without USB serial activated until we can make it non blocking if not connected
// {"serial_log"sv, &serial_logging},
{"name"sv, &name_enable},
}};
@@ -320,6 +323,12 @@ class BLERxView : public View {
{11 * 8, 3 * 16, 20 * 8, 16},
"0/0"};
Checkbox check_serial_log{
{17 * 8, 3 * 16 - 2},
7,
"USB Log",
true};
Console console{
{0, 4 * 16, 240, 240}};
+16 -10
View File
@@ -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);
+8 -6
View File
@@ -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,16 +138,17 @@ class ERTAppView : public View {
const RecentEntriesColumns columns{{
{"ID", 10},
{"Tp", 2},
{"Consumpt", 10},
{"Cnt", 3},
{"Ty", 2},
{"Consumpt", 8},
{"Tamp", 4},
{"Ct", 2},
}};
ERTRecentEntriesView recent_entries_view{columns, recent};
static constexpr auto header_height = 1 * 16;
RxFrequencyField field_frequency{
{5 * 8, 0 * 16},
{0 * 8, 0 * 16},
nav_};
RFAmpField field_rf_amp{
+2 -1
View File
@@ -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();
+3 -1
View File
@@ -85,10 +85,12 @@ ADSBPositionView::ADSBPositionView(
nav.push<GeoMapView>(
geopos.altitude(),
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::HIDDEN,
geopos.lat(),
geopos.lon(),
[this](int32_t altitude, float lat, float lon) {
[this](int32_t altitude, float lat, float lon, int32_t speed) {
geopos.set_altitude(altitude);
geopos.set_speed(speed);
geopos.set_lat(lat);
geopos.set_lon(lon);
});
+2 -1
View File
@@ -58,7 +58,8 @@ class ADSBPositionView : public OptionTabView {
private:
GeoPos geopos{
{0, 2 * 16},
GeoPos::FEET};
GeoPos::FEET,
GeoPos::HIDDEN};
Button button_set_map{
{8 * 8, 6 * 16, 14 * 8, 2 * 16},
+2 -1
View File
@@ -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,*/
+25 -14
View File
@@ -176,12 +176,15 @@ void RegistersWidget::paint(Painter& painter) {
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
const auto pos = screen_pos();
const std::string spaces(config.legend_length(), ' ');
for (uint32_t i = 0; i < config.registers_per_page; i += config.registers_per_row()) {
uint32_t r = page_number * config.registers_per_page + i;
for (uint32_t i = 0; i < config.registers_count; i += config.registers_per_row()) {
const Point offset{
left, static_cast<int>((i / config.registers_per_row()) * row_height)};
const auto text = to_string_hex(i, config.legend_length());
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(r, config.legend_length());
painter.draw_string(
pos + offset,
style().invert(),
@@ -193,15 +196,16 @@ void RegistersWidget::draw_values(
const Coord left,
Painter& painter) {
const auto pos = screen_pos();
const std::string spaces(config.value_length(), ' ');
for (uint32_t i = 0; i < config.registers_per_page; i++) {
uint32_t r = page_number * config.registers_per_page + i;
for (uint32_t i = 0; i < config.registers_count; i++) {
const Point offset = {
static_cast<int>(left + config.legend_width() + 8 + (i % config.registers_per_row()) * (config.value_width() + 8)),
static_cast<int>((i / config.registers_per_row()) * row_height)};
const auto value = reg_read(i);
const auto text = to_string_hex(value, config.value_length());
const auto text = (r >= config.registers_count) ? spaces : to_string_hex(reg_read(r), config.value_length());
painter.draw_string(
pos + offset,
style(),
@@ -213,7 +217,7 @@ uint32_t RegistersWidget::reg_read(const uint32_t register_number) {
if (register_number < config.registers_count) {
switch (config.chip_type) {
case CT_PMEM:
return portapack::persistent_memory::pmem_data_word((page_number * config.registers_count + register_number) / 4) >> (register_number % 4 * 8);
return portapack::persistent_memory::pmem_data_word(register_number / 4) >> (register_number % 4 * 8);
case CT_RFFC5072:
return radio::debug::first_if::register_read(register_number);
case CT_MAX283X:
@@ -272,6 +276,7 @@ RegistersView::RegistersView(
button_done.on_select = [&nav](Button&) { nav.pop(); };
registers_widget.set_parent_rect({0, 48, 240, 192});
registers_widget.set_page(0);
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
static_cast<int>(title.size()) * 8, 16});
@@ -296,6 +301,13 @@ void RegistersView::focus() {
button_done.focus();
}
bool RegistersView::on_encoder(const EncoderEvent delta) {
registers_widget.set_page(std::max(0ul, std::min(registers_widget.page_count() - 1, registers_widget.page() + delta)));
registers_widget.update();
return true;
}
/* ControlsSwitchesWidget ************************************************/
void ControlsSwitchesWidget::on_show() {
@@ -429,10 +441,10 @@ DebugPeripheralsMenuView::DebugPeripheralsMenuView(NavigationView& nav) {
const char* max283x = hackrf_r9 ? "MAX2839" : "MAX2837";
const char* si5351x = hackrf_r9 ? "Si5351A" : "Si5351C";
add_items({
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 16}); }},
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 10}); }},
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 96, 8}); }},
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_bits()}); }},
{"RFFC5072", ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>("RFFC5072", RegistersWidgetConfig{CT_RFFC5072, 31, 31, 16}); }},
{max283x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, max283x]() { nav.push<RegistersView>(max283x, RegistersWidgetConfig{CT_MAX283X, 32, 32, 10}); }},
{si5351x, ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav, si5351x]() { nav.push<RegistersView>(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }},
{audio::debug::codec_name(), ui::Color::dark_cyan(), &bitmap_icon_peripherals_details, [&nav]() { nav.push<RegistersView>(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }},
});
set_max_rows(2); // allow wider buttons
}
@@ -504,8 +516,8 @@ void DebugMemoryDumpView::focus() {
/* DebugPmemView *********************************************************/
DebugPmemView::DebugPmemView(NavigationView& nav)
: registers_widget(RegistersWidgetConfig{CT_PMEM, page_size, 8}) {
add_children({&text_page, &registers_widget, &text_checksum, &text_checksum2, &button_ok});
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({&registers_widget, &text_checksum, &text_checksum2, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
@@ -532,7 +544,6 @@ void DebugPmemView::focus() {
}
void DebugPmemView::update() {
text_page.set(to_string_hex(registers_widget.page() * page_size, 2) + "+");
registers_widget.update();
}
+4 -4
View File
@@ -142,6 +142,7 @@ typedef enum {
struct RegistersWidgetConfig {
chip_type_t chip_type;
uint32_t registers_count;
uint32_t registers_per_page;
uint32_t register_bits;
constexpr size_t legend_length() const {
@@ -194,6 +195,7 @@ class RegistersWidget : public Widget {
void set_page(int32_t value) { page_number = value; }
uint32_t page(void) { return page_number; }
uint32_t page_count(void) { return (config.registers_count + config.registers_per_page - 1) / config.registers_per_page; }
private:
const RegistersWidgetConfig config;
@@ -208,8 +210,8 @@ class RegistersWidget : public Widget {
class RegistersView : public View {
public:
RegistersView(NavigationView& nav, const std::string& title, RegistersWidgetConfig&& config);
void focus();
void focus() override;
bool on_encoder(const EncoderEvent delta) override;
private:
Text text_title{};
@@ -364,8 +366,6 @@ class DebugPmemView : public View {
static constexpr uint8_t page_size{96}; // Must be multiply of 4 otherwise bit shifting for register view wont work properly
static constexpr uint8_t page_count{(portapack::memory::map::backup_ram.size() + page_size - 1) / page_size};
Text text_page{{16, 16, 208, 16}};
RegistersWidget registers_widget;
Text text_checksum{{16, 232, 208, 16}};
+48 -1
View File
@@ -50,6 +50,17 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
this->firmware_selected(path);
}});
}
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.ppfw.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 +76,43 @@ 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".ppfw.tar")) {
// extract, then update
path = extract_tar(u'/' + path).native();
if (path.empty()) return;
}
ui::Painter painter;
painter.fill_rectangle(
{0, 0, portapack::display.width(), portapack::display.height()},
@@ -74,7 +121,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,7 +29,7 @@
#include "ff.h"
#include "baseband_api.hpp"
#include "core_control.hpp"
#include "untar.hpp"
#include <cstdint>
namespace ui {
@@ -55,8 +55,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 */
+2 -1
View File
@@ -412,7 +412,8 @@ void FrequencyEditView::refresh_ui() {
auto is_range = entry_.type == freqman_type::Range;
auto is_ham = entry_.type == freqman_type::HamRadio;
auto has_freq_b = is_range || is_ham;
auto is_repeater = entry_.type == freqman_type::Repeater;
auto has_freq_b = is_range || is_ham || is_repeater;
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
+2 -1
View File
@@ -220,7 +220,8 @@ class FrequencyEditView : public View {
{"Single", 0},
{"Range", 1},
{"HamRadio", 2},
{"Raw", 3},
{"Repeater", 3},
{"Raw", 4},
}};
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
+2 -3
View File
@@ -78,8 +78,6 @@ void NuoptixView::transmit(bool setup) {
}
transmitter_model.set_rf_amp(true);
transmitter_model.set_lna(40);
transmitter_model.set_vga(40);
transmitter_model.enable();
dtmf_message[0] = '*'; // "Pre-tone for restart" method #1
@@ -136,6 +134,7 @@ void NuoptixView::transmit(bool setup) {
shared_memory.bb_data.tones_data.silence = NUOPTIX_TONE_LENGTH; // 49ms tone, 49ms space
audio::set_rate(audio::Rate::Hz_24000);
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, 6 * 2, true, true);
timecode++;
@@ -156,7 +155,7 @@ NuoptixView::NuoptixView(
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.target_frequency(f);
transmitter_model.set_target_frequency(f);
};
};
+277 -50
View File
@@ -22,13 +22,24 @@
*/
#include "ui_recon.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "capture_app.hpp"
#include "convert.hpp"
#include "file.hpp"
#include "file_reader.hpp"
#include "tone_key.hpp"
#include "replay_app.hpp"
#include "string_format.hpp"
#include "ui_fileman.hpp"
#include "io_file.hpp"
#include "io_convert.hpp"
#include "oversample.hpp"
#include "baseband_api.hpp"
#include "metadata_file.hpp"
#include "portapack.hpp"
#include "portapack_persistent_memory.hpp"
#include "utility.hpp"
#include "replay_thread.hpp"
using namespace portapack;
using namespace tonekey;
@@ -37,6 +48,30 @@ namespace fs = std::filesystem;
namespace ui {
void ReconView::reload_restart_recon() {
frequency_file_load();
if (frequency_list.size() > 0) {
if (fwd) {
button_dir.set_text("FW>");
} else {
button_dir.set_text("<RW");
}
recon_resume();
}
if (scanner_mode) {
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
} else {
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
}
void ReconView::check_update_ranges_from_current() {
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
if (update_ranges && !manual_mode) {
@@ -67,7 +102,7 @@ void ReconView::set_loop_config(bool v) {
persistent_memory::set_recon_continuous(continuous);
}
void ReconView::recon_stop_recording() {
void ReconView::recon_stop_recording(bool exiting) {
if (is_recording) {
if (field_mode.selected_index_value() == SPEC_MODULATION)
button_audio_app.set_text("RAW");
@@ -75,13 +110,17 @@ void ReconView::recon_stop_recording() {
button_audio_app.set_text("AUDIO");
button_audio_app.set_style(&Styles::white);
record_view->stop();
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit
button_config.set_style(&Styles::white);
is_recording = false;
// repeater mode
if (!exiting && persistent_memory::recon_repeat_recorded()) {
start_repeat();
}
}
}
void ReconView::clear_freqlist_for_ui_action() {
recon_stop_recording();
recon_stop_recording(false);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
// flag to detect and reload frequency_list
@@ -111,6 +150,9 @@ void ReconView::update_description() {
case freqman_type::HamRadio:
description = "H: ";
break;
case freqman_type::Repeater:
description = "L: ";
break;
default:
description = "S: ";
}
@@ -182,6 +224,7 @@ void ReconView::load_persisted_settings() {
load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios();
load_repeaters = persistent_memory::recon_load_repeaters();
update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked();
}
@@ -265,9 +308,16 @@ void ReconView::focus() {
}
ReconView::~ReconView() {
recon_stop_recording();
if (recon_tx) {
replay_thread.reset();
}
recon_stop_recording(true);
if (field_mode.selected_index_value() != SPEC_MODULATION)
audio::output::stop();
transmitter_model.disable();
receiver_model.disable();
baseband::shutdown();
}
@@ -275,9 +325,20 @@ ReconView::~ReconView() {
ReconView::ReconView(NavigationView& nav)
: nav_{nav} {
chrono_start = chTimeNow();
tx_view.hidden(true);
// set record View
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO",
u"AUTO_AUDIO", u"AUDIO",
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
record_view->set_auto_trim(false);
record_view->hidden(true);
record_view->on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
add_children({&labels,
&field_lna,
&field_vga,
@@ -314,13 +375,9 @@ ReconView::ReconView(NavigationView& nav)
&button_restart,
&button_mic_app,
&button_remove,
record_view.get()});
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
record_view.get(),
&progressbar,
&tx_view});
def_step = 0;
load_persisted_settings();
@@ -492,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;
@@ -566,27 +623,7 @@ ReconView::ReconView(NavigationView& nav)
};
button_restart.on_select = [this](Button&) {
frequency_file_load();
if (frequency_list.size() > 0) {
if (fwd) {
button_dir.set_text("FW>");
} else {
button_dir.set_text("<RW");
}
recon_resume();
}
if (scanner_mode) {
file_name.set_style(&Styles::red);
button_scanner_mode.set_style(&Styles::red);
button_scanner_mode.set_text("SCAN");
} else {
file_name.set_style(&Styles::blue);
button_scanner_mode.set_style(&Styles::blue);
button_scanner_mode.set_text("RECON");
}
if (frequency_list.size() > FREQMAN_MAX_PER_FILE) {
file_name.set_style(&Styles::yellow);
}
reload_restart_recon();
};
button_add.on_select = [this](ButtonWithEncoder&) {
@@ -606,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) {
@@ -704,6 +741,9 @@ ReconView::ReconView(NavigationView& nav)
change_mode(AM_MODULATION); // start on AM.
field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector
// tx progress bar
progressbar.hidden(true);
if (filedelete) {
delete_file(freq_file_path);
}
@@ -741,7 +781,8 @@ void ReconView::frequency_file_load() {
freqman_load_options options{
.load_freqs = load_freqs,
.load_ranges = load_ranges,
.load_hamradios = load_hamradios};
.load_hamradios = load_hamradios,
.load_repeaters = load_repeaters};
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
file_name.set_style(&Styles::red);
desc_cycle.set("...empty file...");
@@ -776,6 +817,12 @@ void ReconView::frequency_file_load() {
}
void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (recon_tx)
return;
if (is_repeat_active())
return;
chrono_end = chTimeNow();
systime_t time_interval = chrono_end - chrono_start;
chrono_start = chrono_end;
@@ -804,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();
}
@@ -835,9 +882,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// contents of a possible recon_start_recording(), but not yet since it's only called once
if (auto_record_locked && !is_recording) {
button_audio_app.set_style(&Styles::red);
if (field_mode.selected_index_value() == SPEC_MODULATION)
if (field_mode.selected_index_value() == SPEC_MODULATION) {
button_audio_app.set_text("RAW REC");
else
} else
button_audio_app.set_text("WAV REC");
record_view->start();
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
@@ -956,6 +1003,26 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
}
}
}
} else if (current_entry().type == freqman_type::Repeater) {
// repeater is like single, we only listen on frequency_a and then jump to next entry
if ((fwd && stepper == 0) || stepper > 0) { // forward
current_index++;
entry_has_changed = true;
// looping
if ((uint32_t)current_index >= frequency_list.size()) {
has_looped = true;
current_index = 0;
}
} else if ((!fwd && stepper == 0) || stepper < 0) {
// reverse
current_index--;
entry_has_changed = true;
// if previous if under the list => go back from end
if (current_index < 0) {
has_looped = true;
current_index = frequency_list.size() - 1;
}
}
}
// set index to boundary if !continuous
if (has_looped && !continuous) {
@@ -984,6 +1051,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (entry_has_changed) {
timer = 0;
switch (current_entry().type) {
case freqman_type::Repeater:
case freqman_type::Single:
freq = current_entry().frequency_a;
break;
@@ -1017,7 +1085,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (stepper > 0) stepper--;
} // if( recon || stepper != 0 || index_stepper != 0 )
} // if (frequency_list.size() > 0 )
} /* on_statistic_updates */
} /* on_statistics_updates */
}
handle_retune();
recon_redraw();
@@ -1080,28 +1148,39 @@ void ReconView::on_stepper_delta(int32_t v) {
}
size_t ReconView::change_mode(freqman_index_t new_mod) {
if (recon_tx || is_repeat_active())
return 0;
field_mode.on_change = [this](size_t, OptionsField::value_t) {};
field_bw.on_change = [this](size_t, OptionsField::value_t) {};
recon_stop_recording();
remove_child(record_view.get());
record_view.reset();
if (new_mod == SPEC_MODULATION) {
recon_stop_recording(false);
if (record_view != nullptr) {
remove_child(record_view.get());
record_view.reset();
}
if (persistent_memory::recon_repeat_recorded()) {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"RECON_REPEAT.C16", u"CAPTURES",
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_as_is(true);
} else if (new_mod == SPEC_MODULATION) {
audio::output::stop();
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_RAW_", u"CAPTURES",
u"AUTO_RAW", u"CAPTURES",
RecordView::FileType::RawS16, 16384, 3);
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO",
u"AUTO_AUDIO", u"AUDIO",
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
}
record_view->set_auto_trim(false);
add_child(record_view.get());
record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [this](std::string message) {
nav_.display_modal("Error", message);
};
receiver_model.disable();
transmitter_model.disable();
baseband::shutdown();
size_t recording_sampling_rate = 0;
switch (new_mod) {
@@ -1144,7 +1223,6 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
// record_view determines the correct oversampling to apply and returns the actual sample rate.
auto actual_sampling_rate = record_view->set_sampling_rate(sampling_rate);
// The radio needs to know the effective sampling rate.
receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
@@ -1222,4 +1300,153 @@ void ReconView::handle_remove_current_item() {
update_description();
}
void ReconView::on_repeat_tx_progress(const uint32_t progress) {
progressbar.set_value(progress);
}
void ReconView::repeat_file_error(const std::filesystem::path& path, const std::string& message) {
nav_.display_modal("Error", "Error opening file \n" + path.string() + "\n" + message);
}
bool ReconView::is_repeat_active() const {
return replay_thread != nullptr;
}
void ReconView::start_repeat() {
// Prepare to send a file.
std::filesystem::path rawfile = u"/" + repeat_rec_path + u"/" + repeat_rec_file;
std::filesystem::path rawmeta = u"/" + repeat_rec_path + u"/" + repeat_rec_meta;
if (recon_tx == false) {
recon_tx = true;
if (record_view != nullptr) {
record_view->stop();
remove_child(record_view.get());
record_view.reset();
}
receiver_model.disable();
transmitter_model.disable();
baseband::shutdown();
baseband::run_image(portapack::spi_flash::image_tag_replay);
size_t rawsize = 0;
{
File capture_file;
auto error = capture_file.open(rawfile);
if (error) {
repeat_file_error(rawfile, "Can't open file to send for size");
return;
}
rawsize = capture_file.size();
}
// Reset the transmit progress bar.
uint8_t sample_size = std::filesystem::capture_file_sample_size(rawfile);
progressbar.set_value(0);
progressbar.set_max(rawsize * sizeof(complex16_t) / sample_size);
progressbar.hidden(false);
auto metadata = read_metadata_file(rawmeta);
// If no metadata found, fallback to the TX frequency.
if (!metadata) {
metadata = {freq, 500'000};
repeat_file_error(rawmeta, "Can't open file to read meta, using default rx_freq,500'000");
}
// Update the sample rate in proc_replay baseband.
baseband::set_sample_rate(metadata->sample_rate,
get_oversample_rate(metadata->sample_rate));
transmitter_model.set_sampling_rate(get_actual_sample_rate(metadata->sample_rate));
transmitter_model.set_baseband_bandwidth(metadata->sample_rate <= 500'000 ? 1'750'000 : 2'500'000); // TX LPF min 1M75 for SR <=500K, and 2M5 (by experimental test) for SR >500K
// set TX to repeater TX freq if entry is Repeater and have a valid freq, else use recorded one
if (current_entry().type == freqman_type::Repeater && current_entry().frequency_b != 0) {
transmitter_model.set_target_frequency(current_entry().frequency_b);
} else {
transmitter_model.set_target_frequency(metadata->center_frequency);
}
// set TX powers and enable transmitter
transmitter_model.set_tx_gain(persistent_memory::recon_repeat_gain());
transmitter_model.set_rf_amp(persistent_memory::recon_repeat_amp());
transmitter_model.enable();
}
// clear replay thread and set reader
replay_thread.reset();
auto reader = std::make_unique<FileConvertReader>();
auto error = reader->open(rawfile);
if (error) {
repeat_file_error(rawfile, "Can't open file to send to thread");
return;
}
// wait for TX if needed (hackish, direct screen update since the UI will be blocked)
if (persistent_memory::recon_repeat_delay() > 0) {
uint8_t delay = persistent_memory::recon_repeat_delay();
Painter p;
while (delay > 0) {
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
// update display information
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Color::light_grey());
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, Styles::red, delay_message);
// sleep 1 second
chThdSleepMilliseconds(1000);
// decre delay
if (delay > 0)
delay = delay - 1;
else
break;
}
}
// ReplayThread starts immediately on construction; must be set before creating.
repeat_ready_signal = true;
repeat_cur_rep++;
replay_thread = std::make_unique<ReplayThread>(
std::move(reader),
/* read_size */ repeat_read_size,
/* buffer_count */ repeat_buffer_count,
&repeat_ready_signal,
[](uint32_t return_code) {
ReplayThreadDoneMessage message{return_code};
EventDispatcher::send_message(message);
});
}
void ReconView::stop_repeat(const bool do_loop) {
repeat_ready_signal = false;
if (is_repeat_active()) {
replay_thread.reset();
transmitter_model.disable();
}
// repeat transmit if current number of repetitions (repeat_cur_rep) is < recon configured number of repetitions (recon_repeat_nb)
if (do_loop && repeat_cur_rep < persistent_memory::recon_repeat_nb()) {
start_repeat();
} else {
repeat_cur_rep = 0;
recon_tx = false;
reload_restart_recon();
progressbar.hidden(true);
set_dirty(); // fix progressbar no hiding
}
}
void ReconView::handle_repeat_thread_done(const uint32_t return_code) {
if (return_code == ReplayThread::END_OF_FILE) {
stop_repeat(true);
} else if (return_code == ReplayThread::READ_ERROR) {
stop_repeat(false);
repeat_file_error(u"/" + repeat_rec_path + u"/" + repeat_rec_file, "Can't open file to send.");
}
}
} /* namespace ui */
+73 -6
View File
@@ -41,10 +41,20 @@
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "ui_recon_settings.hpp"
#include "ui_transmitter.hpp"
#include "replay_thread.hpp"
#include "metadata_file.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_freq_field.hpp"
#include "ui_spectrum.hpp"
#include <string>
#include <memory>
namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
#define RECON_CFG_FILE u"SETTINGS/recon.cfg"
enum class recon_mode : uint8_t {
Recon,
@@ -66,10 +76,17 @@ class ReconView : public View {
private:
NavigationView& nav_;
RxRadioState radio_state_{};
RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
500000 /* sampling rate */
};
app_settings::SettingsManager settings_{
"rx_recon"sv, app_settings::Mode::RX};
"rx_tx_recon"sv, app_settings::Mode::RX_TX};
;
void reload_restart_recon();
void check_update_ranges_from_current();
void set_loop_config(bool v);
void clear_freqlist_for_ui_action();
@@ -93,7 +110,7 @@ class ReconView : public View {
void load_persisted_settings();
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
// placeholder for possible void recon_start_recording();
void recon_stop_recording();
void recon_stop_recording(bool exiting);
// Returns true if 'current_index' is in bounds of frequency_list.
bool current_is_valid();
@@ -125,6 +142,7 @@ class ReconView : public View {
bool load_freqs{true};
bool load_ranges{true};
bool load_hamradios{true};
bool load_repeaters{true};
bool update_ranges{true};
bool fwd{true};
bool recon{true};
@@ -163,6 +181,25 @@ class ReconView : public View {
systime_t chrono_start{};
systime_t chrono_end{};
const std::filesystem::path repeat_rec_file = u"RECON_REPEAT.C16";
const std::filesystem::path repeat_rec_meta = u"RECON_REPEAT.TXT";
const std::filesystem::path repeat_rec_path = u"CAPTURES";
const size_t repeat_read_size{16384};
const size_t repeat_buffer_count{3};
int8_t repeat_cur_rep = 0;
int64_t repeat_sample_rate = 0;
static constexpr uint32_t repeat_bandwidth = 2500000;
void on_repeat_tx_progress(const uint32_t progress);
void start_repeat();
void stop_repeat(const bool do_loop);
bool is_repeat_active() const;
void handle_repeat_thread_done(const uint32_t return_code);
void repeat_file_error(const std::filesystem::path& path, const std::string& message);
std::filesystem::path repeat_file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool repeat_ready_signal{false};
bool recon_tx{false};
// Persisted settings.
SettingsStore ui_settings{
"recon"sv,
@@ -354,17 +391,47 @@ class ReconView : public View {
{168, (35 * 8) - 4, 72, 28},
"<REMOVE>"};
ProgressBar progressbar{
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
/*short_ui*/ true};
MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
this->handle_coded_squelch(message.value);
handle_coded_squelch(message.value);
}};
MessageHandlerRegistration message_handler_stats{
Message::ID::ChannelStatistics,
[this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* p) {
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
handle_repeat_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* p) {
auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
repeat_ready_signal = true;
}
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* p) {
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
on_repeat_tx_progress(message.progress);
}};
};
@@ -99,8 +99,14 @@ void ReconSetupViewMore::save() {
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
persistent_memory::set_recon_load_repeaters(checkbox_load_repeaters.value());
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
persistent_memory::set_recon_repeat_recorded(checkbox_repeat_recorded.value());
persistent_memory::set_recon_repeat_nb(field_repeat_nb.value());
persistent_memory::set_recon_repeat_amp(checkbox_repeat_amp.value());
persistent_memory::set_recon_repeat_gain(field_repeat_gain.value());
persistent_memory::set_recon_repeat_delay(field_repeat_delay.value());
};
void ReconSetupViewMain::focus() {
@@ -113,16 +119,55 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
hidden(true);
add_children({&checkbox_load_freqs,
&checkbox_load_repeaters,
&checkbox_load_ranges,
&checkbox_load_hamradios,
&checkbox_update_ranges_when_recon,
&checkbox_auto_record_locked});
&checkbox_auto_record_locked,
&checkbox_repeat_recorded,
&text_repeat_nb,
&field_repeat_nb,
&checkbox_repeat_amp,
&text_repeat_gain,
&field_repeat_gain,
&text_repeat_delay,
&field_repeat_delay});
// tx options have to be in yellow to inform the users that activating them will make the device transmit
checkbox_repeat_recorded.set_style(&Styles::yellow);
text_repeat_nb.set_style(&Styles::yellow);
field_repeat_nb.set_style(&Styles::yellow);
checkbox_repeat_amp.set_style(&Styles::yellow);
text_repeat_gain.set_style(&Styles::yellow);
field_repeat_gain.set_style(&Styles::yellow);
text_repeat_delay.set_style(&Styles::yellow);
field_repeat_delay.set_style(&Styles::yellow);
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
checkbox_load_repeaters.set_value(persistent_memory::recon_load_repeaters());
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
checkbox_repeat_recorded.set_value(persistent_memory::recon_repeat_recorded());
checkbox_repeat_amp.set_value(persistent_memory::recon_repeat_amp());
field_repeat_nb.set_value(persistent_memory::recon_repeat_nb());
field_repeat_gain.set_value(persistent_memory::recon_repeat_gain());
field_repeat_delay.set_value(persistent_memory::recon_repeat_delay());
// tx warning modal
checkbox_repeat_recorded.on_select = [this, &nav](Checkbox&, bool v) {
if (v) {
nav.display_modal(
"TX WARNING",
"This activate TX ability\nin Recon !",
YESNO,
[this, &nav](bool choice) {
if (!choice)
checkbox_repeat_recorded.set_value(choice);
});
}
};
};
void ReconSetupViewMore::focus() {
@@ -30,6 +30,7 @@
#include "ui_tabview.hpp"
#include "ui_navigation.hpp"
#include "string_format.hpp"
#include "ui_styles.hpp"
// 1Mhz helper
#ifdef OneMHz
@@ -116,26 +117,82 @@ class ReconSetupViewMore : public View {
Checkbox checkbox_load_freqs{
{1 * 8, 12},
3,
"input: load freqs"};
"load freq",
true};
Checkbox checkbox_load_repeaters{
{14 * 8, 12},
3,
"load repeater",
true};
Checkbox checkbox_load_ranges{
{1 * 8, 42},
3,
"input: load ranges"};
"load range",
true};
Checkbox checkbox_load_hamradios{
{1 * 8, 72},
3,
"input: load hamradios"};
"load hamradio",
true};
Checkbox checkbox_update_ranges_when_recon{
{1 * 8, 102},
3,
"auto update m-ranges"};
Checkbox checkbox_auto_record_locked{
{1 * 8, 132},
3,
"record locked periods"};
Checkbox checkbox_repeat_recorded{
{1 * 8, 162},
3,
"repeater,"};
Text text_repeat_nb{
{14 * 8, 165, 3 * 8, 22},
"nb:"};
NumberField field_repeat_nb{
{17 * 8, 165},
2,
{1, 99},
1,
' ',
};
Checkbox checkbox_repeat_amp{
{1 * 8, 192},
3,
"AMP,"};
Text text_repeat_gain{
{9 * 8, 196, 5 * 8, 22},
"GAIN:"};
NumberField field_repeat_gain{
{14 * 8, 196},
2,
{0, 47},
1,
' ',
};
Text text_repeat_delay{
{16 * 8, 196, 8 * 8, 22},
", delay:"};
NumberField field_repeat_delay{
{24 * 8, 196},
3,
{0, 254},
1,
' ',
};
};
class ReconSetupView : public View {
+1
View File
@@ -564,6 +564,7 @@ void ScannerView::frequency_file_load(const fs::path& path) {
def_step_index = entry.step;
switch (entry.type) {
case freqman_type::Repeater:
case freqman_type::Single:
entries.push_back({entry.frequency_a, entry.description});
break;
+16 -39
View File
@@ -133,7 +133,6 @@ SetRadioView::SetRadioView(
&check_clkout,
&field_clkout_freq,
&labels_clkout_khz,
&value_freq_step,
&labels_bias,
&check_bias,
&disable_external_tcxo, // TODO: always show?
@@ -150,23 +149,10 @@ SetRadioView::SetRadioView(
});
}
std::string source_name("---");
switch (reference.source) {
case ClockManager::ReferenceSource::Xtal:
source_name = "HackRF";
break;
case ClockManager::ReferenceSource::PortaPack:
source_name = "PortaPack";
break;
case ClockManager::ReferenceSource::External:
source_name = "External";
break;
}
std::string source_name = clock_manager.get_source();
value_source.set(source_name);
value_source_frequency.set(
to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz");
value_source_frequency.set(clock_manager.get_freq());
// Make these Text controls look like Labels.
label_source.set_style(&Styles::light_grey);
@@ -185,29 +171,20 @@ SetRadioView::SetRadioView(
send_system_refresh();
};
field_clkout_freq.set_value(pmem::clkout_freq());
value_freq_step.set_style(&Styles::light_grey);
// Disallow CLKOUT freq change on hackrf_r9 due to dependencies on GP_CLKIN (same Si5351A clock);
// see comments in ClockManager::enable_clock_output()
if (hackrf_r9) {
if (pmem::clkout_freq() != 10000)
pmem::set_clkout_freq(10000);
field_clkout_freq.set_focusable(false);
}
field_clkout_freq.on_select = [this](NumberField&) {
freq_step_khz++;
if (freq_step_khz > 3) {
freq_step_khz = 0;
}
switch (freq_step_khz) {
case 0:
value_freq_step.set(" |");
break;
case 1:
value_freq_step.set(" | ");
break;
case 2:
value_freq_step.set(" | ");
break;
case 3:
value_freq_step.set("| ");
break;
}
field_clkout_freq.set_step(pow(10, freq_step_khz));
field_clkout_freq.set_value(pmem::clkout_freq());
field_clkout_freq.on_change = [this](SymField&) {
if (field_clkout_freq.to_integer() < 4) // Min. CLK out of Si5351A/B/C-B is 2.5khz , but in our application -intermediate freq 800Mhz-,Min working CLK=4khz.
field_clkout_freq.set_value(4);
if (field_clkout_freq.to_integer() > 60000)
field_clkout_freq.set_value(60000);
};
check_bias.set_value(get_antenna_bias());
@@ -248,7 +225,7 @@ void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) {
SetFrequencyCorrectionModel SetRadioView::form_collect() {
return {
.ppm = static_cast<int8_t>(field_ppm.value()),
.freq = static_cast<uint32_t>(field_clkout_freq.value()),
.freq = static_cast<uint32_t>(field_clkout_freq.to_integer()),
};
}
+2 -8
View File
@@ -163,20 +163,14 @@ class SetRadioView : public View {
13,
"Enable CLKOUT"};
NumberField field_clkout_freq{
SymField field_clkout_freq{
{20 * 8, 6 * 16},
5,
{10, 60000},
1000,
' '};
SymField::Type::Dec};
Labels labels_clkout_khz{
{{26 * 8, 6 * 16}, "kHz", Color::light_grey()}};
Text value_freq_step{
{21 * 8, (7 * 16), 4 * 8, 16},
"| "};
Labels labels_bias{
{{4 * 8 + 4, 8 * 16}, "CAUTION: Ensure that all", Color::red()},
{{5 * 8 + 0, 9 * 16}, "devices attached to the", Color::red()},
+1
View File
@@ -94,6 +94,7 @@ SondeView::SondeView(NavigationView& nav)
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?
+2 -1
View File
@@ -162,7 +162,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},
+46 -19
View File
@@ -217,6 +217,27 @@ ClockManager::Reference ClockManager::get_reference() const {
return reference;
}
std::string ClockManager::get_source() {
std::string source_name("---");
switch (reference.source) {
case ClockManager::ReferenceSource::Xtal:
source_name = "HackRF";
break;
case ClockManager::ReferenceSource::PortaPack:
source_name = "PortaPack";
break;
case ClockManager::ReferenceSource::External:
source_name = "External";
break;
}
return source_name;
}
std::string ClockManager::get_freq() {
return to_string_dec_uint(reference.frequency / 1000000, 2) + "." +
to_string_dec_uint((reference.frequency % 1000000) / 100, 4, '0') + " MHz";
}
static void portapack_tcxo_enable() {
portapack::io.reference_oscillator(true);
@@ -561,26 +582,32 @@ void ClockManager::stop_audio_pll() {
}
void ClockManager::enable_clock_output(bool enable) {
if (enable) {
clock_generator.enable_output(clock_generator_output_og_clkout);
if (portapack::persistent_memory::clkout_freq() < 1000) {
clock_generator.set_ms_frequency(clock_generator_output_og_clkout, portapack::persistent_memory::clkout_freq() * 128000, si5351_vco_f, 7);
} else {
clock_generator.set_ms_frequency(clock_generator_output_og_clkout, portapack::persistent_memory::clkout_freq() * 1000, si5351_vco_f, 0);
}
} else {
clock_generator.disable_output(clock_generator_output_og_clkout);
if (hackrf_r9) {
gpio_r9_clkout_en.output();
gpio_r9_clkout_en.write(enable);
// NOTE: RETURNING HERE IF HACKRF_R9 TO PREVENT CLK2 FROM BEING DISABLED OR FREQ MODIFIED SINCE CLK2 ON R9 IS
// USED FOR BOTH CLKOUT AND FOR THE MCU_CLOCK (== GP_CLKIN) WHICH OTHER LP43XX CLOCKS CURRENTLY RELY ON.
// FUTURE TBD: REMOVE OTHER LP43XX CLOCK DEPENDENCIES ON GP_CLKIN, THEN DELETE THE return LINE BELOW TO ALLOW
// CLKOUT FREQ CHANGES ON R9 BOARDS.
return;
}
auto si5351_clock_control_common = hackrf_r9
? si5351a_clock_control_common
: si5351c_clock_control_common;
const auto ref_pll = hackrf_r9
? ClockControl::MultiSynthSource::PLLA
: get_si5351c_reference_clock_generator_pll(reference.source);
auto clkout_select = hackrf_r9 ? clock_generator_output_r9_clkout : clock_generator_output_og_clkout;
if (enable)
clock_generator.set_clock_control(clock_generator_output_og_clkout, si5351_clock_control_common[clock_generator_output_og_clkout].ms_src(ref_pll).clk_pdn(ClockControl::ClockPowerDown::Power_On));
else
clock_generator.set_clock_control(clock_generator_output_og_clkout, ClockControl::power_off());
if (enable) {
clock_generator.enable_output(clkout_select);
if (portapack::persistent_memory::clkout_freq() < 1000) {
clock_generator.set_ms_frequency(clkout_select, portapack::persistent_memory::clkout_freq() * 128000, si5351_vco_f, 7);
} else {
clock_generator.set_ms_frequency(clkout_select, portapack::persistent_memory::clkout_freq() * 1000, si5351_vco_f, 0);
}
auto si5351_clock_control_common = hackrf_r9 ? si5351a_clock_control_common : si5351c_clock_control_common;
const auto ref_pll = hackrf_r9 ? ClockControl::MultiSynthSource::PLLA : get_si5351c_reference_clock_generator_pll(reference.source);
clock_generator.set_clock_control(clkout_select, si5351_clock_control_common[clkout_select].ms_src(ref_pll).clk_pdn(ClockControl::ClockPowerDown::Power_On));
} else {
clock_generator.disable_output(clkout_select);
clock_generator.set_clock_control(clkout_select, ClockControl::power_off());
}
}
+3
View File
@@ -22,6 +22,7 @@
#ifndef __CLOCK_MANAGER_H__
#define __CLOCK_MANAGER_H__
#include "string_format.hpp"
#include "ch.h"
#include "hal.h"
@@ -74,6 +75,8 @@ class ClockManager {
uint32_t get_frequency_monitor_measurement_in_hertz();
Reference get_reference() const;
std::string get_source();
std::string get_freq();
void enable_clock_output(bool enable);
+24
View File
@@ -238,6 +238,22 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
return nullptr;
}
void EventDispatcher::emulateTouch(ui::TouchEvent event) {
on_touch_event(event);
}
void EventDispatcher::emulateKeyboard(ui::KeyboardEvent event) {
on_keyboard_event(event);
}
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,6 +274,14 @@ 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() {
DisplayFrameSyncMessage message;
message_map.send(&message);
+7
View File
@@ -86,6 +86,12 @@ class EventDispatcher {
events_flag(EVT_MASK_LOCAL);
}
void emulateTouch(ui::TouchEvent event);
void emulateKeyboard(ui::KeyboardEvent event);
ui::Widget* getTopWidget();
ui::Widget* getFocusedWidget();
private:
static Thread* thread_event_loop;
@@ -111,6 +117,7 @@ class EventDispatcher {
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();
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -27,6 +27,8 @@
#ifndef __UI_BLESPAM_H__
#define __UI_BLESPAM_H__
#define BLESPMUSECONSOLE 1
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
@@ -111,9 +113,10 @@ class BLESpamView : public View {
LanguageHelper::currentMessages[LANG_START]};
Checkbox chk_randdev{{100, 16}, 10, "Rnd device", true};
#ifdef BLESPMUSECONSOLE
Console console{
{0, 70, 240, 220}};
#endif
OptionsField options_atkmode{
{0 * 8, 2 * 8},
10,
@@ -134,7 +137,7 @@ class BLESpamView : public View {
bool randomDev{true};
uint8_t channel_number = 37;
char mac[13] = "010203040507";
char mac[13] = "010203040407";
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
@@ -161,6 +164,8 @@ class BLESpamView : public View {
this->on_tx_progress(message.done);
}};
uint8_t packet[80];
// continuity
typedef enum {
@@ -227,7 +232,7 @@ class BLESpamView : public View {
static const uint16_t fastpairModels_count;
typedef struct {
uint32_t value;
const char* name; // could be moved too
// const char* name; // could be moved too
} fpUi32;
static const fpUi32 fastpairModels[];
+12
View File
@@ -44,6 +44,16 @@ 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
)
set(EXTAPPLIST
@@ -58,4 +68,6 @@ set(EXTAPPLIST
lge
lcr
jammer
gpssim
spainter
)
+16 -2
View File
@@ -28,6 +28,8 @@ 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
}
SECTIONS
@@ -88,18 +90,30 @@ 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
}
@@ -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 */
@@ -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
View File
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "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
};
}
+4 -3
View File
@@ -21,6 +21,7 @@
*/
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_widget.hpp"
#include "ui_styles.hpp"
#include "ui_navigation.hpp"
@@ -56,8 +57,8 @@ class RangeView : public View {
const Style& style_info = Styles::grey;
Labels labels{
{{2 * 8, 8 * 8 + 4}, "Start", Color::light_grey()},
{{23 * 8, 8 * 8 + 4}, "Stop", Color::light_grey()},
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Color::light_grey()},
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Color::light_grey()},
{{12 * 8, 5 * 8 - 4}, "Center", Color::light_grey()},
{{12 * 8 + 4, 13 * 8}, "Width", Color::light_grey()}};
@@ -226,7 +227,7 @@ class JammerView : public View {
Button button_transmit{
{148, 216, 80, 80},
"START"};
LanguageHelper::currentMessages[LANG_START]};
MessageHandlerRegistration message_handler_retune{
Message::ID::Retune,
+82
View File
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_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
};
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+19 -6
View File
@@ -185,6 +185,8 @@ bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
} else if (lhs.type == freqman_type::HamRadio) {
equal = lhs.frequency_b == rhs.frequency_b &&
lhs.tone == rhs.tone;
} else if (lhs.type == freqman_type::Repeater) {
equal = lhs.frequency_b == rhs.frequency_b;
}
return equal;
@@ -248,6 +250,10 @@ std::string pretty_string(const freqman_entry& entry, size_t max_length) {
str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
break;
case freqman_type::Repeater:
str = "L:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
break;
case freqman_type::Raw:
str = entry.description;
break;
@@ -292,6 +298,10 @@ std::string to_freqman_string(const freqman_entry& entry) {
if (is_valid(entry.tone))
append_field("c", tonekey::tone_key_value_string(entry.tone));
break;
case freqman_type::Repeater:
append_field("l", to_string_dec_uint(entry.frequency_a));
append_field("t", to_string_dec_uint(entry.frequency_b));
break;
case freqman_type::Raw:
return entry.description;
default:
@@ -372,16 +382,18 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
parse_int(value, entry.frequency_a);
} else if (key == "m") {
entry.modulation = find_by_name(freqman_modulations, value);
} else if (key == "r") {
} else if (key == "r") { // HamRadio relay receive freq
entry.type = freqman_type::HamRadio;
parse_int(value, entry.frequency_a);
} else if (key == "l") { // Portapack Repeater mode listen freq. Used as a single freq if Repeater mode isn't active
entry.type = freqman_type::Repeater;
parse_int(value, entry.frequency_a);
} else if (key == "s") {
entry.step = find_by_name(freqman_steps_short, value);
} else if (key == "t") {
} else if (key == "t") { // Tx freq: scanned as a single freq in HamRadio mode, used as TX freq in Repeater mode and ignored by the scanner
parse_int(value, entry.frequency_b);
}
}
return is_valid(entry);
}
@@ -400,7 +412,8 @@ bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_optio
if (entry.type == freqman_type::Unknown ||
(entry.type == freqman_type::Single && !options.load_freqs) ||
(entry.type == freqman_type::Range && !options.load_ranges) ||
(entry.type == freqman_type::HamRadio && !options.load_hamradios)) {
(entry.type == freqman_type::HamRadio && !options.load_hamradios) ||
(entry.type == freqman_type::Repeater && !options.load_repeaters)) {
continue;
}
@@ -433,8 +446,8 @@ bool is_valid(const freqman_entry& entry) {
if (entry.frequency_a == 0)
return false;
// Frequency B must be set for type Range or Ham Radio
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
// Frequency B must be set for type Range or HamRadio or Repeater
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio || entry.type == freqman_type::Repeater) {
if (entry.frequency_b == 0)
return false;
}
+3 -1
View File
@@ -56,6 +56,7 @@ enum class freqman_type : uint8_t {
Single, // f=
Range, // a=,b=
HamRadio, // r=,t=
Repeater, // l=,t=
Raw, // line content in description
Unknown,
};
@@ -167,6 +168,7 @@ struct freqman_load_options {
bool load_freqs{true};
bool load_ranges{true};
bool load_hamradios{true};
bool load_repeaters{true};
};
using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
@@ -277,4 +279,4 @@ class FreqmanDB {
bool read_raw_{true};
};
#endif /* __FREQMAN_DB_H__ */
#endif /* __FREQMAN_DB_H__ */
+5 -4
View File
@@ -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;
+1 -1
View File
@@ -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();
}
+4
View File
@@ -542,4 +542,8 @@ void shutdown(const bool leave_screen_on) {
shutdown_base();
}
void setEventDispatcherToUSBSerial(EventDispatcher* evt) {
usb_serial.setEventDispatcher(evt);
}
} /* namespace portapack */
+2
View File
@@ -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 */
+305
View File
@@ -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;
}
}
}
/** @} */
+83
View File
@@ -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 4
#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_ */
/** @} */
+22
View File
@@ -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;
+2
View File
@@ -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);
+117 -43
View File
@@ -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,6 +60,7 @@ GeoPos::GeoPos(
// Defaults
set_altitude(0);
set_speed(0);
set_lat(0);
set_lon(0);
@@ -67,10 +72,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;
@@ -79,11 +85,19 @@ GeoPos::GeoPos(
field_lon_seconds.on_change = changed_fn;
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 +112,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,21 +157,38 @@ 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++;
// 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 {
map_zoom++;
// Ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
if (geomap_rect_width % map_zoom != 0) {
map_zoom--;
// When zooming in, ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
if ((map_zoom > 1) && (geomap_rect_width % map_zoom != 0)) {
map_zoom--;
}
}
}
} else if (delta < 0) {
if (map_zoom > -MAX_MAP_ZOOM_OUT) {
if (map_zoom == 1) {
map_zoom = -2;
} else {
map_zoom--;
}
}
} else if ((delta < 0) && (map_zoom > 1)) {
map_zoom--;
} else {
return false;
}
@@ -164,17 +199,31 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
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
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;
}
}
@@ -187,9 +236,12 @@ void GeoMap::draw_markers(Painter& painter) {
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
if (map_zoom != 1) {
if (map_zoom > 1) {
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
} else if (map_zoom < 0) {
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
}
if ((x >= 0) && (x < r.width()) &&
@@ -208,31 +260,39 @@ void GeoMap::draw_markers(Painter& painter) {
}
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, 240> map_line_buffer;
// 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;
if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) {
int32_t zoom_seek_x = x_pos;
int32_t zoom_seek_y = y_pos;
// Adjust starting corner position of map per zoom setting
if (map_zoom > 1) {
zoom_seek_x += (r.width() - (r.width() / map_zoom)) / 2;
zoom_seek_y += (r.height() - (r.height() / map_zoom)) / 2;
} else if (map_zoom < 0) {
zoom_seek_x += (r.width() - (r.width() * (-map_zoom))) / 2;
zoom_seek_y += (r.height() - (r.height() * (-map_zoom))) / 2;
}
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);
// Read from map file and display to zoomed scale
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
map_read_line(map_line_buffer.data(), r.width());
for (uint16_t j = 0; j < duplicate_lines; j++) {
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
}
}
prev_x_pos = x_pos;
prev_y_pos = y_pos;
@@ -255,6 +315,13 @@ void GeoMap::paint(Painter& painter) {
}
}
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);
@@ -323,8 +390,8 @@ bool GeoMap::manual_panning() {
}
void GeoMap::draw_scale(Painter& painter) {
uint16_t km = 100;
uint16_t scale_width = km * pixels_per_km * map_zoom;
uint16_t km = 800;
uint16_t scale_width = (map_zoom > 0) ? km * pixels_per_km * map_zoom : km * pixels_per_km / (-map_zoom);
while (scale_width > screen_width / 2) {
scale_width /= 2;
@@ -413,7 +480,7 @@ void GeoMapView::focus() {
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_position(float lat, float lon, uint16_t angle, int32_t altitude, int32_t speed) {
if (geomap.manual_panning()) {
geomap.set_dirty();
return;
@@ -422,12 +489,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 +515,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 +552,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 +560,7 @@ GeoMapView::GeoMapView(
: nav_(nav),
altitude_(altitude),
altitude_unit_(altitude_unit),
speed_unit_(speed_unit),
lat_(lat),
lon_(lon),
angle_(angle),
@@ -516,12 +588,14 @@ 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;
@@ -540,7 +614,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();
};
}
+39 -9
View File
@@ -29,6 +29,9 @@
#include "portapack.hpp"
#define MAX_MAP_ZOOM_IN 5
#define MAX_MAP_ZOOM_OUT 10
namespace ui {
enum GeoMapMode {
@@ -59,19 +62,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 +92,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},
@@ -159,6 +183,7 @@ 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;
bool init();
void set_mode(GeoMapMode mode);
@@ -187,7 +212,7 @@ class GeoMap : public Widget {
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
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 map_read_line(ui::Color* buffer, uint16_t pixels);
bool manual_panning_{false};
GeoMapMode mode_{};
@@ -220,6 +245,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 +253,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 +266,7 @@ 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);
std::string title() const override { return "Map view"; };
@@ -253,10 +280,12 @@ 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_{};
@@ -266,7 +295,8 @@ class GeoMapView : public View {
GeoPos geopos{
{0, 0},
altitude_unit_};
altitude_unit_,
speed_unit_};
GeoMap geomap{
{0, GeoMap::banner_height, GeoMap::geomap_rect_width, GeoMap::geomap_rect_height}};
+13
View File
@@ -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;
+1
View File
@@ -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();
+33 -1
View File
@@ -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) {
+5
View File
@@ -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;
+17 -4
View File
@@ -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"
@@ -173,6 +173,9 @@ SystemStatusView::SystemStatusView(
pmem::load_persistent_settings_from_file();
}
// configure CLKOUT per pmem setting
portapack::clock_manager.enable_clock_output(pmem::clkout_enabled());
// force apply of selected sdcard speed override at UI startup
pmem::set_config_sdcard_high_speed_io(pmem::config_sdcard_high_speed_io(), false);
@@ -438,7 +441,17 @@ InformationView::InformationView(
&version,
&ltime});
#if GCC_VERSION_MISMATCH
static constexpr Style style_gcc_warning{
.font = font::fixed_8x16,
.background = {33, 33, 33},
.foreground = Color::yellow(),
};
version.set_style(&style_gcc_warning);
#else
version.set_style(&style_infobar);
#endif
ltime.set_style(&style_infobar);
refresh();
set_dirty();
@@ -594,7 +607,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
{"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>(); }},
//{"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }}, //moved to ext
//{"Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav]() { nav.push<JammerView>(); }}, //moved to ext
// { "Key fob", ui::Color::orange(), &bitmap_icon_keyfob, [&nav](){ nav.push<KeyfobView>(); }},
// {"LGE", ui::Color::yellow(), &bitmap_icon_lge, [&nav]() { nav.push<LGEView>(); }}, //moved to ext
@@ -604,7 +617,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) {
{"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>(); }},
//{"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>(); }},
+13 -2
View File
@@ -145,6 +145,15 @@ OversampleRate RecordView::get_oversample_rate(uint32_t sample_rate) {
// Setter for datetime and frequency filename
void RecordView::set_filename_date_frequency(bool set) {
filename_date_frequency = set;
if (set)
filename_as_is = false;
}
// Setter for leaving the filename untouched
void RecordView::set_filename_as_is(bool set) {
filename_as_is = set;
if (set)
filename_date_frequency = false;
}
bool RecordView::is_active() const {
@@ -186,9 +195,11 @@ void RecordView::start() {
base_path = filename_stem_pattern.string() + "_" + date_time + "_" +
trim(to_string_freq(receiver_model.target_frequency())) + "Hz";
base_path = folder / base_path;
} else {
} else if (filename_as_is) {
base_path = filename_stem_pattern.string();
base_path = folder / base_path;
} else
base_path = next_filename_matching_pattern(folder / filename_stem_pattern);
}
if (base_path.empty()) {
return;
+2
View File
@@ -73,6 +73,7 @@ class RecordView : public View {
bool is_active() const;
void set_filename_date_frequency(bool set);
void set_filename_as_is(bool set);
private:
void toggle();
@@ -91,6 +92,7 @@ class RecordView : public View {
// Time Stamp
bool filename_date_frequency = false;
bool filename_as_is = false;
rtc::RTC datetime{};
const std::filesystem::path filename_stem_pattern;
+1 -1
View File
@@ -32,7 +32,7 @@ void USBSerial::dispatch() {
if (shell_created == false) {
shell_created = true;
create_shell();
create_shell(_eventDispatcher);
}
bulk_out_receive();
+5
View File
@@ -24,6 +24,8 @@
#include "ch.h"
#include "hal.h"
class EventDispatcher;
namespace portapack {
class USBSerial {
@@ -32,6 +34,7 @@ class USBSerial {
void dispatch();
void on_channel_opened();
void on_channel_closed();
void setEventDispatcher(EventDispatcher* ed) { _eventDispatcher = ed; }
private:
void enable_xtal();
@@ -43,6 +46,8 @@ class USBSerial {
bool connected{false};
bool shell_created{false};
EventDispatcher* _eventDispatcher = NULL;
};
} // namespace portapack
+513 -12
View File
@@ -27,19 +27,36 @@
#include "png_writer.hpp"
#include "irq_controls.hpp"
#include "portapack.hpp"
#include "portapack_hal.hpp"
#include "hackrf_gpio.hpp"
#include "jtag_target_gpio.hpp"
#include "cpld_max5.hpp"
#include "portapack_cpld_data.hpp"
#include "crc.hpp"
#include "hackrf_cpld_data.hpp"
#include "usb_serial_io.h"
#include "ff.h"
#include "chprintf.h"
#include "chqueues.h"
#include "untar.hpp"
#include "ui_widget.hpp"
#include <string>
#include <codecvt>
#include <cstring>
#include <locale>
#include <libopencm3/lpc43xx/wwdt.h>
#define SHELL_WA_SIZE THD_WA_SIZE(1024 * 3)
#define palOutputPad(port, pad) (LPC_GPIO->DIR[(port)] |= 1 << (pad))
static EventDispatcher* _eventDispatcherInstance = NULL;
static EventDispatcher* getEventDispatcherInstance() {
return _eventDispatcherInstance;
}
// queue handler from ch
static msg_t qwait(GenericQueue* qp, systime_t time) {
if (TIME_IMMEDIATE == time)
@@ -146,6 +163,15 @@ std::filesystem::path path_from_string8(char* path) {
return conv.from_bytes(path);
}
bool strEndsWith(const std::u16string& str, const std::u16string& suffix) {
if (str.length() >= suffix.length()) {
std::u16string endOfString = str.substr(str.length() - suffix.length());
return endOfString == suffix;
} else {
return false;
}
}
static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "Usage: flash /FIRMWARE/portapack-h1_h2-mayhem.bin\r\n");
@@ -153,15 +179,37 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
}
auto path = path_from_string8(argv[0]);
size_t filename_length = strlen(argv[0]);
if (!std::filesystem::file_exists(path)) {
chprintf(chp, "file not found.\r\n");
return;
}
std::memcpy(&shared_memory.bb_data.data[0], path.c_str(), (filename_length + 1) * 2);
// check file extensions
if (strEndsWith(path.native(), u".ppfw.tar")) {
// extract tar
chprintf(chp, "Extracting TAR file.\r\n");
auto res = UnTar::untar(
path.native(), [chp](const std::string fileName) {
chprintf(chp, fileName.c_str());
chprintf(chp, "\r\n");
});
if (res.empty()) {
chprintf(chp, "error bad TAR file.\r\n");
return;
}
path = res; // it will contain the last bin file in tar
} else if (strEndsWith(path.native(), u".bin")) {
// nothing to do for this case yet.
} else {
chprintf(chp, "error only .bin or .ppfw.tar files canbe flashed.\r\n");
return;
}
chprintf(chp, "Flashing: ");
chprintf(chp, path.string().c_str());
chprintf(chp, "\r\n");
chThdSleepMilliseconds(50);
std::memcpy(&shared_memory.bb_data.data[0], path.native().c_str(), (path.native().length() + 1) * 2);
m4_request_shutdown();
chThdSleepMilliseconds(50);
m4_init(portapack::spi_flash::image_tag_flash_utility, portapack::memory::map::m4_code, false);
@@ -296,6 +344,66 @@ static void cmd_button(BaseSequentialStream* chp, int argc, char* argv[]) {
chprintf(chp, "ok\r\n");
}
static void cmd_touch(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 2) {
chprintf(chp, "usage: touch x y\r\n");
return;
}
int x = (int)strtol(argv[0], NULL, 10);
int y = (int)strtol(argv[1], NULL, 10);
if (x < 0 || x > ui::screen_width || y < 0 || y > ui::screen_height) {
chprintf(chp, "usage: touch x y\r\n");
return;
}
auto evtd = getEventDispatcherInstance();
if (evtd == NULL) {
chprintf(chp, "error\r\n");
}
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::Start});
evtd->emulateTouch({{x, y}, ui::TouchEvent::Type::End});
chprintf(chp, "ok\r\n");
}
// send ascii keys in 2 char hex representation. Can send multiple keys at once like: keyboard 414243 (this will be ABC)
static void cmd_keyboard(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: keyboard XX\r\n");
return;
}
auto evtd = getEventDispatcherInstance();
if (evtd == NULL) {
chprintf(chp, "error\r\n");
}
size_t data_string_len = strlen(argv[0]);
if (data_string_len % 2 != 0) {
chprintf(chp, "usage: keyboard XXXX\r\n");
return;
}
for (size_t i = 0; i < data_string_len; i++) {
char c = argv[0][i];
if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
chprintf(chp, "usage: keyboard XX\r\n");
return;
}
}
char buffer[3] = {0, 0, 0};
for (size_t i = 0; i < data_string_len / 2; i++) {
buffer[0] = argv[0][i * 2];
buffer[1] = argv[0][i * 2 + 1];
uint8_t chr = (uint8_t)strtol(buffer, NULL, 16);
evtd->emulateKeyboard(chr);
}
chprintf(chp, "ok\r\n");
}
static void cmd_sd_list_dir(BaseSequentialStream* chp, int argc, char* argv[]) {
if (argc != 1) {
chprintf(chp, "usage: ls /\r\n");
@@ -418,21 +526,18 @@ static void cmd_sd_read(BaseSequentialStream* chp, int argc, char* argv[]) {
int size = (int)strtol(argv[0], NULL, 10);
uint8_t buffer[16];
uint8_t buffer[62];
do {
File::Size bytes_to_read = size > 16 ? 16 : size;
File::Size bytes_to_read = size > 62 ? 62 : size;
auto bytes_read = shell_file->read(buffer, bytes_to_read);
if (bytes_read.is_error()) {
chprintf(chp, "error %d\r\n", bytes_read.error());
return;
}
for (size_t i = 0; i < bytes_read.value(); i++)
chprintf(chp, "%02X", buffer[i]);
chprintf(chp, "\r\n");
std::string res = to_string_hex_array(buffer, bytes_read.value());
res += "\r\n";
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)res.c_str(), res.size());
if (bytes_to_read != bytes_read.value())
return;
@@ -479,6 +584,395 @@ static void cmd_sd_write(BaseSequentialStream* chp, int argc, char* argv[]) {
chprintf(chp, "ok\r\n");
}
static void cpld_info(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage =
"usage: cpld_info <device>\r\n"
" supported modes:\r\n"
" cpld_info hackrf\r\n"
" cpld_info portapack\r\n";
if (argc != 1) {
chprintf(chp, usage);
return;
}
if (strncmp(argv[0], "hackrf", 5) == 0) {
jtag::GPIOTarget jtag_target_hackrf_cpld{
hackrf::one::gpio_cpld_tck,
hackrf::one::gpio_cpld_tms,
hackrf::one::gpio_cpld_tdi,
hackrf::one::gpio_cpld_tdo,
};
hackrf::one::cpld::CPLD hackrf_cpld{jtag_target_hackrf_cpld};
{
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
hackrf_cpld.prepare_read_eeprom();
for (const auto& block : hackrf::one::cpld::verify_blocks) {
auto from_device = hackrf_cpld.read_block_eeprom(block.id);
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
auto bit = *i;
crc.process_bit(bit);
}
}
hackrf_cpld.finalize_read_eeprom();
chprintf(chp, "CPLD eeprom firmware checksum: 0x%08X\r\n", crc.checksum());
}
{
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
hackrf_cpld.prepare_read_sram();
for (const auto& block : hackrf::one::cpld::verify_blocks) {
auto from_device = hackrf_cpld.read_block_sram(block);
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
auto bit = *i;
crc.process_bit(bit);
}
}
hackrf_cpld.finalize_read_sram(hackrf::one::cpld::verify_blocks[0].id);
chprintf(chp, "CPLD sram firmware checksum: 0x%08X\r\n", crc.checksum());
}
} else if (strncmp(argv[0], "portapack", 5) == 0) {
jtag::GPIOTarget target{
portapack::gpio_cpld_tck,
portapack::gpio_cpld_tms,
portapack::gpio_cpld_tdi,
portapack::gpio_cpld_tdo};
jtag::JTAG jtag{target};
portapack::cpld::CPLD cpld{jtag};
cpld.reset();
cpld.run_test_idle();
uint32_t idcode = cpld.get_idcode();
chprintf(chp, "CPLD IDCODE: 0x%08X\r\n", idcode);
if (idcode == 0x20A50DD) {
chprintf(chp, "CPLD Model: Altera MAX V 5M40Z\r\n");
cpld.reset();
cpld.run_test_idle();
cpld.sample();
cpld.bypass();
cpld.enable();
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
cpld.prepare_read(0x0000);
for (size_t i = 0; i < 3328; i++) {
uint16_t data = cpld.read();
crc.process_byte((data >> 0) & 0xff);
crc.process_byte((data >> 8) & 0xff);
crc.process_byte((data >> 16) & 0xff);
crc.process_byte((data >> 24) & 0xff);
}
cpld.prepare_read(0x0001);
for (size_t i = 0; i < 512; i++) {
uint16_t data = cpld.read();
crc.process_byte((data >> 0) & 0xff);
crc.process_byte((data >> 8) & 0xff);
crc.process_byte((data >> 16) & 0xff);
crc.process_byte((data >> 24) & 0xff);
}
chprintf(chp, "CPLD firmware checksum: 0x%08X\r\n", crc.checksum());
m4_request_shutdown();
chThdSleepMilliseconds(1000);
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
WWDT_TC = 100000 & 0xFFFFFF;
WWDT_FEED_SEQUENCE;
} else if (idcode == 0x00025610) {
chprintf(chp, "CPLD Model: AGM AG256SL100\r\n");
if (cpld.AGM_enter_maintenance_mode() == false) {
return;
}
cpld.AGM_enter_read_mode();
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
for (size_t i = 0; i < 2048; i++) {
uint32_t data = cpld.AGM_read(i);
crc.process_byte((data >> 0) & 0xff);
crc.process_byte((data >> 8) & 0xff);
crc.process_byte((data >> 16) & 0xff);
crc.process_byte((data >> 24) & 0xff);
}
cpld.AGM_exit_maintenance_mode();
chprintf(chp, "CPLD firmware checksum: 0x%08X\r\n", crc.checksum());
m4_request_shutdown();
chThdSleepMilliseconds(1000);
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
WWDT_TC = 100000 & 0xFFFFFF;
WWDT_FEED_SEQUENCE;
} else {
chprintf(chp, "CPLD Model: unknown\r\n");
}
} else {
chprintf(chp, usage);
}
}
// walks throught the given widget's childs in recurse to get all support text and pass it to a callback function
static void widget_collect_accessibility(BaseSequentialStream* chp, ui::Widget* w, void (*callback)(BaseSequentialStream*, const std::string&, const std::string&), ui::Widget* focusedWidget) {
for (auto child : w->children()) {
if (!child->hidden()) {
std::string res = "";
child->getAccessibilityText(res);
std::string strtype = "";
child->getWidgetName(strtype);
if (child == focusedWidget) strtype += "*";
if (callback != NULL && !res.empty()) callback(chp, res, strtype);
widget_collect_accessibility(chp, child, callback, focusedWidget);
}
}
}
// callback when it found any response from a widget
static void accessibility_callback(BaseSequentialStream* chp, const std::string& strResult, const std::string& wgType) {
if (!wgType.empty()) {
chprintf(chp, "[");
chprintf(chp, wgType.c_str());
chprintf(chp, "] ");
}
chprintf(chp, "%s\r\n", strResult.c_str());
}
// gets all widget's accessibility helper text
static void cmd_accessibility_readall(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argc;
(void)argv;
auto evtd = getEventDispatcherInstance();
if (evtd == NULL) {
chprintf(chp, "error Can't get Event Dispatcherr\n");
return;
}
auto wg = evtd->getTopWidget();
if (wg == NULL) {
chprintf(chp, "error Can't get top Widget\r\n");
return;
}
auto focused = evtd->getFocusedWidget();
widget_collect_accessibility(chp, wg, accessibility_callback, focused);
chprintf(chp, "ok\r\n");
}
// gets focused widget's accessibility helper text
static void cmd_accessibility_readcurr(BaseSequentialStream* chp, int argc, char* argv[]) {
(void)argc;
(void)argv;
auto evtd = getEventDispatcherInstance();
if (evtd == NULL) {
chprintf(chp, "error Can't get Event Dispatcher\r\n");
return;
}
auto wg = evtd->getFocusedWidget();
if (wg == NULL) {
chprintf(chp, "error Can't get focused Widget\r\n");
return;
}
std::string res = "";
wg->getAccessibilityText(res);
if (res.empty()) {
// try with parent
wg = wg->parent();
if (wg == NULL) {
chprintf(chp, "error Widget not providing accessibility info\r\n");
return;
}
wg->getAccessibilityText(res);
if (res.empty()) {
chprintf(chp, "error Widget not providing accessibility info\r\n");
return;
}
}
std::string strtype = "";
wg->getWidgetName(strtype);
accessibility_callback(chp, res, strtype);
chprintf(chp, "\r\nok\r\n");
}
static void cmd_cpld_read(BaseSequentialStream* chp, int argc, char* argv[]) {
const char* usage =
"usage: cpld_read <device> <target>\r\n"
" device can be: hackrf, portapack\r\n"
" target can be: sram (hackrf only), eeprom\r\n";
if (argc != 2) {
chprintf(chp, usage);
return;
}
if (strncmp(argv[0], "hackrf", 5) == 0) {
if (strncmp(argv[1], "eeprom", 5) == 0) {
jtag::GPIOTarget jtag_target_hackrf_cpld{
hackrf::one::gpio_cpld_tck,
hackrf::one::gpio_cpld_tms,
hackrf::one::gpio_cpld_tdi,
hackrf::one::gpio_cpld_tdo,
};
hackrf::one::cpld::CPLD hackrf_cpld{jtag_target_hackrf_cpld};
hackrf_cpld.prepare_read_eeprom();
for (const auto& block : hackrf::one::cpld::verify_blocks) {
auto from_device = hackrf_cpld.read_block_eeprom(block.id);
chprintf(chp, "bank %04X: ", block.id);
uint32_t n = 6;
uint8_t byte = 0;
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
auto bit = *i;
byte |= bit << (7 - (n % 8));
if (n % 8 == 7) {
chprintf(chp, "%02X ", byte);
byte = 0;
}
n++;
}
chprintf(chp, "\r\n");
}
hackrf_cpld.finalize_read_eeprom();
}
else if (strncmp(argv[1], "sram", 5) == 0) {
jtag::GPIOTarget jtag_target_hackrf_cpld{
hackrf::one::gpio_cpld_tck,
hackrf::one::gpio_cpld_tms,
hackrf::one::gpio_cpld_tdi,
hackrf::one::gpio_cpld_tdo,
};
hackrf::one::cpld::CPLD hackrf_cpld{jtag_target_hackrf_cpld};
hackrf_cpld.prepare_read_sram();
for (const auto& block : hackrf::one::cpld::verify_blocks) {
auto from_device = hackrf_cpld.read_block_sram(block);
chprintf(chp, "bank %04X: ", block.id);
uint32_t n = 6;
uint8_t byte = 0;
for (std::array<bool, 274UL>::reverse_iterator i = from_device.rbegin(); i != from_device.rend(); ++i) {
auto bit = *i;
byte |= bit << (7 - (n % 8));
if (n % 8 == 7) {
chprintf(chp, "%02X ", byte);
byte = 0;
}
n++;
}
chprintf(chp, "\r\n");
}
hackrf_cpld.finalize_read_sram(hackrf::one::cpld::verify_blocks[0].id);
}
} else if (strncmp(argv[0], "portapack", 5) == 0) {
jtag::GPIOTarget target{
portapack::gpio_cpld_tck,
portapack::gpio_cpld_tms,
portapack::gpio_cpld_tdi,
portapack::gpio_cpld_tdo};
jtag::JTAG jtag{target};
portapack::cpld::CPLD cpld{jtag};
cpld.reset();
cpld.run_test_idle();
uint32_t idcode = cpld.get_idcode();
chprintf(chp, "CPLD IDCODE: 0x%08X\r\n", idcode);
if (idcode == 0x20A50DD) {
chprintf(chp, "CPLD Model: Altera MAX V 5M40Z\r\n");
cpld.reset();
cpld.run_test_idle();
cpld.sample();
cpld.bypass();
cpld.enable();
cpld.prepare_read(0x0000);
for (size_t i = 0; i < 3328; i++) {
uint16_t data = cpld.read();
chprintf(chp, "%d: 0x%04X\r\n", i, data);
}
cpld.prepare_read(0x0001);
for (size_t i = 0; i < 512; i++) {
uint16_t data = cpld.read();
chprintf(chp, "%d: 0x%04X\r\n", i, data);
}
m4_request_shutdown();
chThdSleepMilliseconds(1000);
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
WWDT_TC = 100000 & 0xFFFFFF;
WWDT_FEED_SEQUENCE;
} else if (idcode == 0x00025610) {
chprintf(chp, "CPLD Model: AGM AG256SL100\r\n");
if (cpld.AGM_enter_maintenance_mode() == false) {
return;
}
cpld.AGM_enter_read_mode();
for (size_t i = 0; i < 2048; i++) {
uint32_t data = cpld.AGM_read(i);
if (i % 4 == 0)
chprintf(chp, "%5d: ", i * 4);
chprintf(chp, "0x%08X", data);
if (i % 4 == 3)
chprintf(chp, "\r\n");
else
chprintf(chp, " ");
}
cpld.AGM_exit_maintenance_mode();
m4_request_shutdown();
chThdSleepMilliseconds(1000);
WWDT_MOD = WWDT_MOD_WDEN | WWDT_MOD_WDRESET;
WWDT_TC = 100000 & 0xFFFFFF;
WWDT_FEED_SEQUENCE;
} else {
chprintf(chp, "CPLD Model: unknown\r\n");
}
} else {
chprintf(chp, usage);
}
}
static const ShellCommand commands[] = {
{"reboot", cmd_reboot},
{"dfu", cmd_dfu},
@@ -491,6 +985,8 @@ static const ShellCommand commands[] = {
{"write_memory", cmd_write_memory},
{"read_memory", cmd_read_memory},
{"button", cmd_button},
{"touch", cmd_touch},
{"keyboard", cmd_keyboard},
{"ls", cmd_sd_list_dir},
{"rm", cmd_sd_delete},
{"open", cmd_sd_open},
@@ -499,12 +995,17 @@ static const ShellCommand commands[] = {
{"read", cmd_sd_read},
{"write", cmd_sd_write},
{"filesize", cmd_sd_filesize},
{"cpld_info", cpld_info},
{"cpld_read", cmd_cpld_read},
{"accessibility_readall", cmd_accessibility_readall},
{"accessibility_readcurr", cmd_accessibility_readcurr},
{NULL, NULL}};
static const ShellConfig shell_cfg1 = {
(BaseSequentialStream*)&SUSBD1,
commands};
void create_shell() {
void create_shell(EventDispatcher* evtd) {
_eventDispatcherInstance = evtd;
shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO);
}
+3 -1
View File
@@ -26,4 +26,6 @@
#include "shell.h"
void create_shell(void);
class EventDispatcher;
void create_shell(EventDispatcher* evtd);
+14 -13
View File
@@ -476,13 +476,6 @@ set(MODE_CPPSRC
)
DeclareTargets(PREP replay)
### GPS Simulator
set(MODE_CPPSRC
proc_gps_sim.cpp
)
DeclareTargets(PGPS gps_sim)
### Signal generator
set(MODE_CPPSRC
@@ -490,12 +483,6 @@ set(MODE_CPPSRC
)
DeclareTargets(PSIG siggen)
### Spectrum painter
set(MODE_CPPSRC
proc_spectrum_painter.cpp
)
DeclareTargets(PSPT spectrum_painter)
### SSTV TX
@@ -637,6 +624,20 @@ set(MODE_CPPSRC
)
DeclareTargets(PAMT am_tv)
### GPS Simulator
set(MODE_CPPSRC
proc_gps_sim.cpp
)
DeclareTargets(PGPS gps_sim)
### Spectrum painter
set(MODE_CPPSRC
proc_spectrum_painter.cpp
)
DeclareTargets(PSPT spectrum_painter)
### Test
set(MODE_CPPSRC
+69 -2
View File
@@ -147,9 +147,12 @@ void CPLD::sector_select(const uint16_t id) {
}
bool CPLD::idcode_ok() {
return (get_idcode() == idcode);
}
uint32_t CPLD::get_idcode() {
shift_ir(instruction_t::IDCODE);
const auto idcode_read = jtag.shift_dr(idcode_length, 0);
return (idcode_read == idcode);
return jtag.shift_dr(idcode_length, 0);
}
std::array<uint16_t, 5> CPLD::read_silicon_id() {
@@ -208,6 +211,16 @@ void CPLD::program_block(
}
}
void CPLD::prepare_read(uint16_t block) {
sector_select(block);
shift_ir(instruction_t::ISC_READ);
jtag.runtest_tck(93); // 5us
}
uint32_t CPLD::read() {
return jtag.shift_dr(16, 0xffff) & 0xfbff;
}
bool CPLD::verify_block(
const uint16_t id,
const uint16_t* const data,
@@ -265,5 +278,59 @@ bool CPLD::is_blank() {
return block_0_blank && block_1_blank;
}
bool CPLD::AGM_enter_maintenance_mode() {
shift_ir(instruction_t::AGM_STAGE_1);
jtag.runtest_tck(100);
shift_ir(instruction_t::AGM_STAGE_2);
jtag.runtest_tck(100);
shift_ir(instruction_t::AGM_STAGE_1);
jtag.runtest_tck(100);
shift_ir(instruction_t::AGM_SET_REGISTER);
jtag.runtest_tck(100);
jtag.shift_dr(8, 0x0);
jtag.runtest_tck(100);
shift_ir(instruction_t::AGM_PROGRAM);
jtag.runtest_tck(100);
jtag.shift_dr(32, 0x203f0044uL, 0x80000000);
shift_ir(instruction_t::IDCODE);
jtag.runtest_tck(100);
auto idcode = jtag.shift_dr(idcode_length, 0);
return idcode == 0x00025610;
}
void CPLD::AGM_exit_maintenance_mode() {
shift_ir(instruction_t::AGM_RESET);
jtag.runtest_tck(100);
}
void CPLD::AGM_enter_read_mode() {
shift_ir(instruction_t::AGM_SET_REGISTER);
jtag.runtest_tck(100);
jtag.shift_dr(8, 0xf0);
jtag.runtest_tck(100);
shift_ir(instruction_t::AGM_READ);
jtag.runtest_tck(100);
}
uint32_t CPLD::AGM_encode_address(uint32_t address, uint32_t trailer) {
uint32_t p = trailer;
for (size_t i = 0; i < 18; i++) {
auto address_bit = (address >> i) & 0x01;
p |= address_bit << (31 - i);
}
return p;
}
uint32_t CPLD::AGM_read(uint32_t address) {
auto encoded_address = AGM_encode_address(address * 4, 0xC0);
return jtag.shift_dr(32, encoded_address, 0x0);
}
} /* namespace max5 */
} /* namespace cpld */
+17
View File
@@ -60,6 +60,7 @@ class CPLD {
}
bool idcode_ok();
uint32_t get_idcode();
void enable();
@@ -90,6 +91,15 @@ class CPLD {
std::pair<bool, uint8_t> boundary_scan();
void prepare_read(uint16_t block);
uint32_t read();
bool AGM_enter_maintenance_mode();
void AGM_exit_maintenance_mode();
void AGM_enter_read_mode();
uint32_t AGM_encode_address(uint32_t address, uint32_t trailer);
uint32_t AGM_read(uint32_t address);
private:
using idcode_t = uint32_t;
static constexpr size_t idcode_length = 32;
@@ -115,6 +125,13 @@ class CPLD {
ISC_ADDRESS_SHIFT = 0b1000000011, // 0x203
ISC_READ = 0b1000000101, // 0x205
ISC_NOOP = 0b1000010000, // 0x210
AGM_RESET = 0x3f7,
AGM_STAGE_1 = 0x3f8,
AGM_STAGE_2 = 0x3f9,
AGM_PROGRAM = 0x3fa,
AGM_SET_REGISTER = 0x3fc,
AGM_READ = 0x3fd,
AGM_ERASE = 0x3fe,
};
void shift_ir(const instruction_t instruction) {
+75 -11
View File
@@ -19,6 +19,7 @@
* Boston, MA 02110-1301, USA.
*/
#include "ch.h"
#include "cpld_xilinx.hpp"
namespace cpld {
@@ -47,6 +48,25 @@ void XC2C64A::write_sram(const verify_blocks_t& blocks) {
}
bool XC2C64A::verify_sram(const verify_blocks_t& blocks) {
prepare_read_sram();
const jtag::tap::bits_t empty_row{block_length};
auto error = false;
for (const auto& block : blocks) {
tap.shift({&block.id, block_id_length}, true);
tap.state(state_t::run_test_idle);
tap.state(state_t::shift_dr);
error |= tap.shift(empty_row, {block.data.data(), block_length}, {block.mask.data(), block_length}, false, nullptr);
}
finalize_read_sram(blocks[0].id);
return !error;
}
void XC2C64A::prepare_read_sram() {
tap.set_repeat(0);
tap.set_end_ir(state_t::run_test_idle);
tap.set_end_dr(state_t::run_test_idle);
@@ -61,17 +81,25 @@ bool XC2C64A::verify_sram(const verify_blocks_t& blocks) {
tap.state(state_t::shift_dr);
tap.shift(empty_row, false);
}
auto error = false;
for (const auto& block : blocks) {
tap.shift({&block.id, block_id_length}, true);
tap.state(state_t::run_test_idle);
std::array<bool, 274> XC2C64A::read_block_sram(verify_block_t block) {
tap.shift({&block.id, block_id_length}, true);
tap.state(state_t::run_test_idle);
tap.state(state_t::shift_dr);
error |= tap.shift(empty_row, {block.data.data(), block_length}, {block.mask.data(), block_length}, false);
}
// Redundant operation to finish the row.
tap.shift({&blocks[0].id, block_id_length}, true);
tap.state(state_t::shift_dr);
const jtag::tap::bits_t empty_row{block_length};
std::vector<bool> from_device;
tap.shift(empty_row, {block.data.data(), block_length}, {block.mask.data(), block_length}, false, &from_device);
std::array<bool, block_length> ret;
std::copy_n(std::make_move_iterator(from_device.begin()), block_length, ret.begin());
return ret;
}
void XC2C64A::finalize_read_sram(block_id_t id) {
tap.shift({&id, block_id_length}, true);
tap.state(state_t::run_test_idle);
tap.set_end_dr(state_t::run_test_idle);
@@ -79,8 +107,6 @@ bool XC2C64A::verify_sram(const verify_blocks_t& blocks) {
bypass();
tap.state(state_t::test_logic_reset);
return !error;
}
bool XC2C64A::verify_eeprom(const verify_blocks_t& blocks) {
@@ -133,6 +159,44 @@ void XC2C64A::init_from_eeprom() {
tap.state(state_t::test_logic_reset);
}
void XC2C64A::prepare_read_eeprom() {
tap.set_repeat(0);
tap.set_end_ir(state_t::run_test_idle);
tap.set_end_dr(state_t::run_test_idle);
reset();
bypass();
enable();
shift_ir(instruction_t::ISC_READ);
}
std::array<bool, 274> XC2C64A::read_block_eeprom(block_id_t id) {
const jtag::tap::bits_t empty_row{block_length};
tap.set_end_dr(state_t::pause_dr);
tap.shift_dr({&id, block_id_length});
tap.set_end_ir(state_t::run_test_idle);
tap.wait(state_t::pause_dr, state_t::pause_dr, 20);
tap.set_end_ir(state_t::run_test_idle);
tap.wait(state_t::run_test_idle, state_t::run_test_idle, 100);
std::vector<bool> from_device = tap.shift_dr_read(empty_row);
tap.wait(state_t::run_test_idle, state_t::run_test_idle, 100);
std::array<bool, block_length> ret;
std::copy_n(std::make_move_iterator(from_device.begin()), block_length, ret.begin());
return ret;
}
void XC2C64A::finalize_read_eeprom() {
disable();
bypass();
tap.state(state_t::test_logic_reset);
}
bool XC2C64A::shift_ir(const instruction_t instruction) {
const ir_t ir_buffer = toUType(instruction);
const jtag::tap::bits_t bits{&ir_buffer, ir_length};
+7
View File
@@ -68,6 +68,13 @@ class XC2C64A {
bool verify_eeprom(const verify_blocks_t& blocks);
void init_from_eeprom();
void prepare_read_eeprom();
void prepare_read_sram();
std::array<bool, block_length> read_block_eeprom(block_id_t id);
std::array<bool, block_length> read_block_sram(verify_block_t block);
void finalize_read_eeprom();
void finalize_read_sram(block_id_t id);
private:
static constexpr size_t idcode_length = 32;
using idcode_t = uint32_t;
+13
View File
@@ -82,6 +82,19 @@ CommodityType Packet::commodity_type() const {
return invalid_commodity_type;
}
TamperFlags Packet::tamper_flags() const {
if (type() == Type::SCM) {
return (reader_.read(9, 2) << 4) | reader_.read(3, 2); // Physical/Encoder tamper flags in lower/upper nibbles
}
if (type() == Type::SCMPLUS) {
return reader_.read(10 * 8, 16);
}
if (type() == Type::IDM) {
return reader_.read(11 * 8, 48);
}
return invalid_tamper_flags;
}
FormattedSymbols Packet::symbols_formatted() const {
return format_symbols(decoder_);
}
+3
View File
@@ -34,10 +34,12 @@ namespace ert {
using ID = uint32_t;
using Consumption = uint32_t;
using CommodityType = uint32_t;
using TamperFlags = uint32_t;
constexpr ID invalid_id = 0;
constexpr CommodityType invalid_commodity_type = -1;
constexpr Consumption invalid_consumption = 0;
constexpr TamperFlags invalid_tamper_flags = 0;
class Packet {
public:
@@ -67,6 +69,7 @@ class Packet {
ID id() const;
CommodityType commodity_type() const;
Consumption consumption() const;
TamperFlags tamper_flags() const;
FormattedSymbols symbols_formatted() const;
+1
View File
@@ -68,6 +68,7 @@ constexpr size_t clock_generator_output_og_mcu_clkin = 7;
constexpr size_t clock_generator_output_r9_if = 0;
constexpr size_t clock_generator_output_r9_sgpio = 1;
constexpr size_t clock_generator_output_r9_clkout = 2;
constexpr size_t clock_generator_output_r9_mcu_clkin = 2;
/* ADC0 */
+11
View File
@@ -37,4 +37,15 @@ uint32_t JTAG::shift(const size_t count, uint32_t value) {
return value;
}
uint32_t JTAG::shift_header(const size_t count, uint32_t value) {
for (size_t i = 0; i < count; i++) {
const auto tdo = target.clock(
0,
value & 1);
value >>= 1;
value |= tdo << (count - 1);
}
return value;
}
} /* namespace jtag */
+19
View File
@@ -89,10 +89,29 @@ class JTAG {
return result;
}
uint32_t shift_dr(const size_t count, const uint32_t address, const uint32_t value) {
/* Run-Test/Idle -> Select-DR-Scan */
target.clock(1, 0);
/* Scan -> Capture -> Shift */
target.clock(0, 0);
target.clock(0, 0);
shift_header(count, address);
const auto result = shift(count, value);
/* Exit1 -> Update */
target.clock(1, 0);
/* Update -> Run-Test/Idle */
target.clock(0, 0);
return result;
}
private:
Target& target;
uint32_t shift(const size_t count, uint32_t value);
uint32_t shift_header(const size_t count, uint32_t value);
};
} /* namespace jtag */
+18 -2
View File
@@ -191,6 +191,10 @@ bool TAPMachine::shift_dr(const bits_t& tdi_value, const bits_t& tdo_expected, c
return shift_data(tdi_value, tdo_expected, tdo_mask, state_t::shift_dr, _end_dr, _run_test);
}
std::vector<bool> TAPMachine::shift_dr_read(const bits_t& tdi_value) {
return shift_data_read(tdi_value, state_t::shift_dr, _end_dr, _run_test);
}
void TAPMachine::state(const state_t state) {
if (state == state_t::test_logic_reset) {
for (int i = 0; i < 5; i++) {
@@ -227,7 +231,7 @@ void TAPMachine::shift_start(const state_t state) {
advance_to_state(state);
}
bool TAPMachine::shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms) {
bool TAPMachine::shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms, std::vector<bool>* from_device) {
if (tdo_expected.length() != tdo_mask.length()) {
return false;
}
@@ -239,6 +243,10 @@ bool TAPMachine::shift(const bits_t& tdi, const bits_t& tdo_expected, const bits
for (uint32_t i = 0; i < tdi.length(); i++) {
const auto tms = end_tms & (i == (tdi.length() - 1));
const auto tdo = clock(tms, tdi[i]);
if (from_device != nullptr)
from_device->push_back(tdo);
if (tdo_expected && tdo_mask) {
tdo_error |= (tdo & tdo_mask[i]) != (tdo_expected[i] & tdo_mask[i]);
}
@@ -258,7 +266,15 @@ void TAPMachine::shift_end(const state_t end_state, const uint32_t end_delay) {
bool TAPMachine::shift_data(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const state_t state, const state_t end_state, const uint32_t end_delay) {
shift_start(state);
const auto result = shift(tdi, tdo_expected, tdo_mask, true);
const auto result = shift(tdi, tdo_expected, tdo_mask, true, nullptr);
shift_end(end_state, end_delay);
return result;
}
std::vector<bool> TAPMachine::shift_data_read(const bits_t& tdi, const state_t state, const state_t end_state, const uint32_t end_delay) {
shift_start(state);
std::vector<bool> result;
shift(tdi, {}, {}, true, &result);
shift_end(end_state, end_delay);
return result;
}
+5 -2
View File
@@ -26,6 +26,7 @@
#include <cstdint>
#include <cstddef>
#include <vector>
namespace jtag {
namespace tap {
@@ -113,13 +114,14 @@ class TAPMachine {
void set_end_dr(const state_t state);
bool shift(const bits_t& tdi, const bool end_tms) {
return shift(tdi, {}, {}, end_tms);
return shift(tdi, {}, {}, end_tms, nullptr);
}
bool shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms);
bool shift(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const bool end_tms, std::vector<bool>* from_device);
bool shift_ir(const bits_t& tdi_value, const bits_t& tdo_expected = {}, const bits_t& tdo_mask = {});
bool shift_dr(const bits_t& tdi_value, const bits_t& tdo_expected = {}, const bits_t& tdo_mask = {});
std::vector<bool> shift_dr_read(const bits_t& tdi_value);
void state(const state_t state);
@@ -142,6 +144,7 @@ class TAPMachine {
void shift_end(const state_t end_state, const uint32_t end_delay);
bool shift_data(const bits_t& tdi, const bits_t& tdo_expected, const bits_t& tdo_mask, const state_t state, const state_t end_state, const uint32_t end_delay);
std::vector<bool> shift_data_read(const bits_t& tdi, const state_t state, const state_t end_state, const uint32_t end_delay);
};
} /* namespace tap */
@@ -77,7 +77,7 @@ constexpr modem_repeat_range_t modem_repeat_range{1, 99};
constexpr int32_t modem_repeat_reset_value{5};
using clkout_freq_range_t = range_t<uint32_t>;
constexpr clkout_freq_range_t clkout_freq_range{10, 60000};
constexpr clkout_freq_range_t clkout_freq_range{4, 60000}; // Min. CLK out of Si5351A/B/C-B is 2.5khz , but in our application -intermediate freq 800Mhz-,Min working CLK=4khz.
constexpr uint16_t clkout_freq_reset_value{10000};
enum data_structure_version_enum : uint32_t {
@@ -195,6 +195,9 @@ struct data_t {
// Recon App
uint64_t recon_config;
int8_t recon_repeat_nb;
int8_t recon_repeat_gain;
uint8_t recon_repeat_delay;
// enable or disable converter
bool converter;
@@ -257,7 +260,11 @@ struct data_t {
tone_mix(tone_mix_reset_value),
hardware_config(0),
recon_config(0),
recon_repeat_nb(0),
recon_repeat_gain(0),
recon_repeat_delay(0),
converter(false),
updown_converter(false),
@@ -284,11 +291,11 @@ struct data_t {
struct backup_ram_t {
private:
volatile uint32_t regfile[63];
volatile uint32_t regfile[PMEM_SIZE_WORDS - 1];
volatile uint32_t check_value;
static void copy(const backup_ram_t& src, backup_ram_t& dst) {
for (size_t i = 0; i < 63; i++) {
for (size_t i = 0; i < PMEM_SIZE_WORDS - 1; i++) {
dst.regfile[i] = src.regfile[i];
}
dst.check_value = src.check_value;
@@ -297,7 +304,7 @@ struct backup_ram_t {
static void copy_from_data_t(const data_t& src, backup_ram_t& dst) {
const uint32_t* const src_words = (uint32_t*)&src;
const size_t word_count = (sizeof(data_t) + 3) / 4;
for (size_t i = 0; i < 63; i++) {
for (size_t i = 0; i < PMEM_SIZE_WORDS - 1; i++) {
if (i < word_count) {
dst.regfile[i] = src_words[i];
} else {
@@ -308,7 +315,7 @@ struct backup_ram_t {
uint32_t compute_check_value() {
CRC<32> crc{0x04c11db7, 0xffffffff, 0xffffffff};
for (size_t i = 0; i < 63; i++) {
for (size_t i = 0; i < PMEM_SIZE_WORDS - 1; i++) {
const auto word = regfile[i];
crc.process_byte((word >> 0) & 0xff);
crc.process_byte((word >> 8) & 0xff);
@@ -391,10 +398,16 @@ void defaults() {
set_recon_continuous(true);
set_recon_clear_output(false);
set_recon_load_freqs(true);
set_recon_load_repeaters(true);
set_recon_load_ranges(true);
set_recon_update_ranges_when_recon(true);
set_recon_load_hamradios(true);
set_recon_match_mode(0);
set_recon_repeat_recorded(false);
set_recon_repeat_amp(false);
set_recon_repeat_gain(35);
set_recon_repeat_nb(3);
set_recon_repeat_delay(1);
set_config_sdcard_high_speed_io(false, true);
}
@@ -747,6 +760,24 @@ bool recon_match_mode() {
bool recon_auto_record_locked() {
return (data->recon_config & 0x00400000UL) ? true : false;
}
bool recon_repeat_recorded() {
return (data->recon_config & 0x00200000UL) ? true : false;
}
int8_t recon_repeat_nb() {
return data->recon_repeat_nb;
}
int8_t recon_repeat_gain() {
return data->recon_repeat_gain;
}
uint8_t recon_repeat_delay() {
return data->recon_repeat_delay;
}
bool recon_repeat_amp() {
return (data->recon_config & 0x00100000UL) ? true : false;
}
bool recon_load_repeaters() {
return (data->recon_config & 0x00080000UL) ? true : false;
}
void set_recon_autosave_freqs(const bool v) {
data->recon_config = (data->recon_config & ~0x80000000UL) | (v << 31);
@@ -778,6 +809,24 @@ void set_recon_match_mode(const bool v) {
void set_recon_auto_record_locked(const bool v) {
data->recon_config = (data->recon_config & ~0x00400000UL) | (v << 22);
}
void set_recon_repeat_recorded(const bool v) {
data->recon_config = (data->recon_config & ~0x00200000UL) | (v << 21);
}
void set_recon_repeat_nb(const int8_t v) {
data->recon_repeat_nb = v;
}
void set_recon_repeat_gain(const int8_t v) {
data->recon_repeat_gain = v;
}
void set_recon_repeat_delay(const uint8_t v) {
data->recon_repeat_delay = v;
}
void set_recon_repeat_amp(const bool v) {
data->recon_config = (data->recon_config & ~0x00100000UL) | (v << 20);
}
void set_recon_load_repeaters(const bool v) {
data->recon_config = (data->recon_config & ~0x00080000UL) | (v << 19);
}
/* UI Config 2 */
bool ui_hide_speaker() {
@@ -988,6 +1037,9 @@ bool debug_dump() {
pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(data->tone_mix));
pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(data->hardware_config));
pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(data->recon_config, 16));
pmem_dump_file.write_line("recon_repeat_nb: " + to_string_dec_int(data->recon_repeat_nb, 16));
pmem_dump_file.write_line("recon_repeat_gain:" + to_string_hex(data->recon_config, 16));
pmem_dump_file.write_line("recon_repeat_delay:" + to_string_hex(data->recon_config, 16));
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->converter));
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->updown_converter));
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(data->updown_frequency_rx_correction));
@@ -39,6 +39,9 @@
// persistant memory from/to sdcard flag file
#define PMEM_SETTING_FILE u"/SETTINGS/pmem_settings"
#define PMEM_SIZE_BYTES 256 // total amount of pmem space in bytes, including checksum
#define PMEM_SIZE_WORDS (PMEM_SIZE_BYTES / 4)
using namespace modems;
using namespace serializer;
@@ -242,11 +245,17 @@ bool recon_autostart_recon();
bool recon_continuous();
bool recon_clear_output();
bool recon_load_freqs();
bool recon_load_repeaters();
bool recon_load_ranges();
bool recon_update_ranges_when_recon();
bool recon_auto_record_locked();
bool recon_repeat_recorded();
int8_t recon_repeat_nb();
int8_t recon_repeat_gain();
bool recon_repeat_amp();
bool recon_load_hamradios();
bool recon_match_mode();
uint8_t recon_repeat_delay();
void set_recon_autosave_freqs(const bool v);
void set_recon_autostart_recon(const bool v);
void set_recon_continuous(const bool v);
@@ -255,8 +264,14 @@ void set_recon_load_freqs(const bool v);
void set_recon_load_ranges(const bool v);
void set_recon_update_ranges_when_recon(const bool v);
void set_recon_auto_record_locked(const bool v);
void set_recon_repeat_recorded(const bool v);
void set_recon_repeat_nb(const int8_t v);
void set_recon_repeat_gain(const int8_t v);
void set_recon_repeat_amp(const bool v);
void set_recon_load_hamradios(const bool v);
void set_recon_load_repeaters(const bool v);
void set_recon_match_mode(const bool v);
void set_recon_repeat_delay(const uint8_t v);
/* UI Config 2 */
bool ui_hide_speaker();
+1
View File
@@ -371,6 +371,7 @@ enum class KeyEvent : uint8_t {
};
using EncoderEvent = int32_t;
using KeyboardEvent = uint8_t;
struct TouchEvent {
enum class Type : uint32_t {
+1 -1
View File
@@ -1,6 +1,6 @@
#include "ui_language.hpp"
const char* LanguageHelper::englishMessages[] = {"OK", "Cancel", "Error", "Modem setup", "Debug", "Log", "Done", "Start", "Stop", "Scan", "Clear", "Ready", "Data:"};
const char* LanguageHelper::englishMessages[] = {"OK", "Cancel", "Error", "Modem setup", "Debug", "Log", "Done", "Start", "Stop", "Scan", "Clear", "Ready", "Data:", "Loop"};
const char** LanguageHelper::currentMessages = englishMessages;
+2 -1
View File
@@ -18,7 +18,8 @@ enum LangConsts {
LANG_SCAN,
LANG_CLEAR,
LANG_READY,
LANG_DATADP
LANG_DATADP,
LANG_LOOP
};
class LanguageHelper {
+216
View File
@@ -172,6 +172,10 @@ bool Widget::on_touch(const TouchEvent event) {
(void)event;
return false;
}
bool Widget::on_keyboard(const KeyboardEvent event) {
(void)event;
return false;
}
const std::vector<Widget*>& Widget::children() const {
return no_children;
@@ -233,6 +237,12 @@ void Widget::dirty_overlapping_children_in_rect(const Rect& child_rect) {
}
}
void Widget::getAccessibilityText(std::string& result) {
result = "";
}
void Widget::getWidgetName(std::string& result) {
result = "";
}
/* View ******************************************************************/
void View::paint(Painter& painter) {
@@ -363,6 +373,12 @@ void Text::set(std::string_view value) {
set_dirty();
}
void Text::getAccessibilityText(std::string& result) {
result = text;
}
void Text::getWidgetName(std::string& result) {
result = "Text";
}
void Text::paint(Painter& painter) {
const auto rect = screen_rect();
auto s = has_focus() ? style().invert() : style();
@@ -403,6 +419,17 @@ void Labels::paint(Painter& painter) {
}
}
void Labels::getAccessibilityText(std::string& result) {
result = "";
for (auto& label : labels_) {
result += label.text;
result += ", ";
}
}
void Labels::getWidgetName(std::string& result) {
result = "Labels";
}
/* LiveDateTime **********************************************************/
void LiveDateTime::on_tick_second() {
@@ -575,6 +602,13 @@ void ProgressBar::set_value(const uint32_t value) {
set_dirty();
}
void ProgressBar::getAccessibilityText(std::string& result) {
result = to_string_dec_uint(_value) + " / " + to_string_dec_uint(_max);
}
void ProgressBar::getWidgetName(std::string& result) {
result = "ProgressBar";
}
void ProgressBar::paint(Painter& painter) {
int v_scaled;
@@ -675,6 +709,13 @@ void Console::write(std::string message) {
}
}
void Console::getAccessibilityText(std::string& result) {
result = "{" + buffer + "}";
}
void Console::getWidgetName(std::string& result) {
result = "Console";
}
void Console::writeln(std::string message) {
write(message + "\n");
}
@@ -783,6 +824,13 @@ bool Checkbox::set_value(const bool value) {
return false;
}
void Checkbox::getAccessibilityText(std::string& result) {
result = text_ + ((value_) ? " checked" : " unchecked");
}
void Checkbox::getWidgetName(std::string& result) {
result = "Checkbox";
}
bool Checkbox::value() const {
return value_;
}
@@ -853,6 +901,11 @@ bool Checkbox::on_key(const KeyEvent key) {
return false;
}
bool Checkbox::on_keyboard(const KeyboardEvent event) {
if (event == 10 || event == 32) return set_value(not value_);
return false;
}
bool Checkbox::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
@@ -895,6 +948,13 @@ std::string Button::text() const {
return text_;
}
void Button::getAccessibilityText(std::string& result) {
result = text_;
}
void Button::getWidgetName(std::string& result) {
result = "Button";
}
void Button::paint(Painter& painter) {
Color bg, fg;
const auto r = screen_rect();
@@ -944,6 +1004,16 @@ bool Button::on_key(const KeyEvent key) {
return false;
}
bool Button::on_keyboard(const KeyboardEvent event) {
if (event == 10 || event == 32) {
if (on_select) {
on_select(*this);
return true;
}
}
return false;
}
bool Button::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
@@ -1031,6 +1101,13 @@ std::string ButtonWithEncoder::text() const {
return text_;
}
void ButtonWithEncoder::getAccessibilityText(std::string& result) {
result = text_;
}
void ButtonWithEncoder::getWidgetName(std::string& result) {
result = "ButtonWithEncoder";
}
void ButtonWithEncoder::paint(Painter& painter) {
Color bg, fg;
const auto r = screen_rect();
@@ -1080,6 +1157,16 @@ bool ButtonWithEncoder::on_key(const KeyEvent key) {
return false;
}
bool ButtonWithEncoder::on_keyboard(const KeyboardEvent key) {
if (key == 32 || key == 10) {
if (on_select) {
on_select(*this);
return true;
}
}
return false;
}
bool ButtonWithEncoder::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
@@ -1176,6 +1263,13 @@ void NewButton::set_text(const std::string value) {
set_dirty();
}
void NewButton::getAccessibilityText(std::string& result) {
result = text_;
}
void NewButton::getWidgetName(std::string& result) {
result = "NewButton";
}
std::string NewButton::text() const {
return text_;
}
@@ -1274,6 +1368,16 @@ bool NewButton::on_key(const KeyEvent key) {
return false;
}
bool NewButton::on_keyboard(const KeyboardEvent key) {
if (key == 32 || key == 10) {
if (on_select) {
on_select();
return true;
}
}
return false;
}
bool NewButton::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
@@ -1360,6 +1464,13 @@ ImageButton::ImageButton(
set_focusable(true);
}
void ImageButton::getAccessibilityText(std::string& result) {
result = "image";
}
void ImageButton::getWidgetName(std::string& result) {
result = "ImageButton";
}
bool ImageButton::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (on_select) {
@@ -1371,6 +1482,16 @@ bool ImageButton::on_key(const KeyEvent key) {
return false;
}
bool ImageButton::on_keyboard(const KeyboardEvent key) {
if (key == 32 || key == 10) {
if (on_select) {
on_select(*this);
return true;
}
}
return false;
}
bool ImageButton::on_touch(const TouchEvent event) {
switch (event.type) {
case TouchEvent::Type::Start:
@@ -1440,6 +1561,13 @@ bool ImageToggle::value() const {
return value_;
}
void ImageToggle::getAccessibilityText(std::string& result) {
result = value_ ? "checked" : "unchecked";
}
void ImageToggle::getWidgetName(std::string& result) {
result = "ImageToggle";
}
void ImageToggle::set_value(bool b) {
if (b == value_)
return;
@@ -1475,6 +1603,13 @@ size_t ImageOptionsField::selected_index_value() const {
return options[selected_index_].second;
}
void ImageOptionsField::getAccessibilityText(std::string& result) {
result = "selected index: " + to_string_dec_uint(selected_index_);
}
void ImageOptionsField::getWidgetName(std::string& result) {
result = "ImageOptionsField";
}
void ImageOptionsField::set_selected_index(const size_t new_index) {
if (new_index < options.size()) {
if (new_index != selected_index()) {
@@ -1537,6 +1672,12 @@ bool ImageOptionsField::on_encoder(const EncoderEvent delta) {
return true;
}
bool ImageOptionsField::on_keyboard(const KeyboardEvent key) {
if (key == '+' || key == ' ' || key == 10) return on_encoder(1);
if (key == '-' || key == 8) return on_encoder(-1);
return false;
}
bool ImageOptionsField::on_touch(const TouchEvent event) {
if (event.type == TouchEvent::Type::Start) {
focus();
@@ -1568,6 +1709,20 @@ const OptionsField::value_t& OptionsField::selected_index_value() const {
return options_[selected_index_].second;
}
void OptionsField::getAccessibilityText(std::string& result) {
result = "options: ";
bool first = true;
for (const auto& option : options_) {
if (!first) result += " ,";
first = false;
result += option.first;
}
result += "; selected: " + selected_index_name();
}
void OptionsField::getWidgetName(std::string& result) {
result = "OptionsField";
}
void OptionsField::set_selected_index(const size_t new_index, bool trigger_change) {
if (new_index < options_.size()) {
if (new_index != selected_index() || trigger_change) {
@@ -1653,6 +1808,11 @@ bool OptionsField::on_encoder(const EncoderEvent delta) {
set_selected_index(new_value);
return true;
}
bool OptionsField::on_keyboard(const KeyboardEvent key) {
if (key == '+' || key == ' ' || key == 10) return on_encoder(1);
if (key == '-' || key == 8) return on_encoder(-1);
return false;
}
bool OptionsField::on_touch(const TouchEvent event) {
if (event.type == TouchEvent::Type::Start) {
@@ -1681,6 +1841,13 @@ const std::string& TextEdit::value() const {
return text_;
}
void TextEdit::getAccessibilityText(std::string& result) {
result = text_;
}
void TextEdit::getWidgetName(std::string& result) {
result = "TextEdit";
}
void TextEdit::set_cursor(uint32_t pos) {
cursor_pos_ = std::min<size_t>(pos, text_.length());
set_dirty();
@@ -1772,6 +1939,19 @@ bool TextEdit::on_key(const KeyEvent key) {
return true;
}
bool TextEdit::on_keyboard(const KeyboardEvent key) {
// if ascii printable
if (key >= 0x20 && key <= 0x7e) {
char_add(key);
return true;
}
if (key == 8) {
char_delete();
return true;
}
return false;
}
bool TextEdit::on_encoder(const EncoderEvent delta) {
int32_t new_pos = cursor_pos_ + delta;
@@ -1817,6 +1997,13 @@ const std::string& TextField::get_text() const {
return text;
}
void TextField::getAccessibilityText(std::string& result) {
result = text;
}
void TextField::getWidgetName(std::string& result) {
result = "TextField";
}
void TextField::set_text(std::string_view value) {
set(value);
if (on_change)
@@ -1872,6 +2059,13 @@ int32_t NumberField::value() const {
return value_;
}
void NumberField::getAccessibilityText(std::string& result) {
result = to_string_dec_int(value_);
}
void NumberField::getWidgetName(std::string& result) {
result = "NumberField";
}
void NumberField::set_value(int32_t new_value, bool trigger_change) {
if (can_loop) {
if (new_value >= range.first)
@@ -1928,6 +2122,22 @@ bool NumberField::on_encoder(const EncoderEvent delta) {
return true;
}
bool NumberField::on_keyboard(const KeyboardEvent key) {
if (key == 10) {
if (on_select) {
on_select(*this);
return true;
}
}
if (key == '+' || key == ' ') {
return on_encoder(1);
}
if (key == '-' || key == 8) {
return on_encoder(-1);
}
return false;
}
bool NumberField::on_touch(const TouchEvent event) {
if (event.type == TouchEvent::Type::Start) {
focus();
@@ -2066,6 +2276,12 @@ const std::string& SymField::to_string() const {
return value_;
}
void SymField::getAccessibilityText(std::string& result) {
result = value_;
}
void SymField::getWidgetName(std::string& result) {
result = "SymField";
}
void SymField::paint(Painter& painter) {
Point p = screen_pos();
+53
View File
@@ -101,10 +101,14 @@ class Widget {
virtual bool on_key(const KeyEvent event);
virtual bool on_encoder(const EncoderEvent event);
virtual bool on_touch(const TouchEvent event);
virtual bool on_keyboard(const KeyboardEvent event);
virtual const std::vector<Widget*>& children() const;
virtual Context& context() const;
virtual void getAccessibilityText(std::string& result);
virtual void getWidgetName(std::string& result);
void set_style(const Style* new_style);
const Style& style() const;
@@ -206,6 +210,8 @@ class Text : public Widget {
void set(std::string_view value);
void paint(Painter& painter) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
protected:
// NB: Don't truncate this string. The UI will only render
@@ -233,6 +239,8 @@ class Labels : public Widget {
void set_labels(std::initializer_list<Label> labels);
void paint(Painter& painter) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
std::vector<Label> labels_;
@@ -311,6 +319,8 @@ class ProgressBar : public Widget {
void set_value(const uint32_t value);
void paint(Painter& painter) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
uint32_t _value = 0;
@@ -344,6 +354,8 @@ class Console : public Widget {
void enable_scrolling(bool enable);
void on_show() override;
void on_hide() override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
Point pos{0, 0};
@@ -382,7 +394,10 @@ class Checkbox : public Widget {
void paint(Painter& painter) override;
bool on_key(const KeyEvent key) override;
bool on_keyboard(const KeyboardEvent key) override;
bool on_touch(const TouchEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
std::string text_;
@@ -418,6 +433,9 @@ class Button : public Widget {
void on_focus() override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
std::string text_;
@@ -456,6 +474,10 @@ class ButtonWithEncoder : public Widget {
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_encoder(const EncoderEvent delta) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
std::string text_;
@@ -490,6 +512,10 @@ class NewButton : public Widget {
void on_focus() override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
void paint(Painter& painter) override;
@@ -544,6 +570,9 @@ class ImageButton : public Image {
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
};
/* A button that toggles between two images when set. */
@@ -577,6 +606,9 @@ class ImageToggle : public ImageButton {
bool value() const;
void set_value(bool b);
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
// Hide this field.
using ImageButton::on_select;
@@ -621,6 +653,9 @@ class ImageOptionsField : public Widget {
void on_focus() override;
bool on_encoder(const EncoderEvent delta) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
options_t options;
@@ -658,6 +693,10 @@ class OptionsField : public Widget {
void on_focus() override;
bool on_encoder(const EncoderEvent delta) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
const size_t length_;
@@ -702,6 +741,10 @@ class TextEdit : public Widget {
bool on_key(const KeyEvent key) override;
bool on_encoder(const EncoderEvent delta) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
void on_focus() override;
void on_blur() override;
@@ -729,6 +772,9 @@ class TextField : public Text {
bool on_encoder(EncoderEvent delta) override;
bool on_touch(TouchEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
using Text::set;
};
@@ -763,6 +809,10 @@ class NumberField : public Widget {
bool on_key(const KeyEvent key) override;
bool on_encoder(const EncoderEvent delta) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
range_t range;
@@ -839,6 +889,9 @@ class SymField : public Widget {
bool on_encoder(EncoderEvent delta) override;
bool on_touch(TouchEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
/* Ensure the specified symbol is in the symbol list. */
char ensure_valid(char symbol) const;

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